| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/public/cpp/smartlock_state.h" |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "base/version.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/ash/login/easy_unlock/chrome_proximity_auth_client.h" |
| #include "chrome/browser/ash/login/easy_unlock/easy_unlock_notification_controller.h" |
| #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.h" |
| #include "chrome/browser/ash/login/easy_unlock/smartlock_feature_usage_metrics.h" |
| #include "chrome/browser/ash/login/session/user_session_manager.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_dialog.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chromeos/ash/components/dbus/dbus_thread_manager.h" |
| #include "chromeos/ash/components/login/auth/public/user_context.h" |
| #include "chromeos/ash/components/multidevice/logging/logging.h" |
| #include "chromeos/ash/components/proximity_auth/proximity_auth_profile_pref_manager.h" |
| #include "chromeos/ash/components/proximity_auth/proximity_auth_system.h" |
| #include "chromeos/ash/components/proximity_auth/screenlock_bridge.h" |
| #include "chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h" |
| #include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h" |
| #include "chromeos/dbus/power/power_manager_client.h" |
| #include "components/account_id/account_id.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/session_manager/core/session_manager.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "components/version_info/version_info.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| enum class SmartLockEnabledState { |
| ENABLED = 0, |
| DISABLED = 1, |
| UNSET = 2, |
| COUNT |
| }; |
| |
| void LogSmartLockEnabledState(SmartLockEnabledState state) { |
| UMA_HISTOGRAM_ENUMERATION("SmartLock.EnabledState", state, |
| SmartLockEnabledState::COUNT); |
| } |
| |
| void SetAuthTypeIfChanged( |
| proximity_auth::ScreenlockBridge::LockHandler* lock_handler, |
| const AccountId& account_id, |
| proximity_auth::mojom::AuthType auth_type, |
| const std::u16string& auth_value) { |
| DCHECK(lock_handler); |
| const proximity_auth::mojom::AuthType existing_auth_type = |
| lock_handler->GetAuthType(account_id); |
| if (auth_type == existing_auth_type) |
| return; |
| |
| lock_handler->SetAuthType(account_id, auth_type, auth_value); |
| } |
| |
| } // namespace |
| |
| // static |
| EasyUnlockService* EasyUnlockService::Get(Profile* profile) { |
| return EasyUnlockServiceFactory::GetForBrowserContext(profile); |
| } |
| |
| // static |
| EasyUnlockService* EasyUnlockService::GetForUser( |
| const user_manager::User& user) { |
| Profile* profile = ProfileHelper::Get()->GetProfileByUser(&user); |
| if (!profile) |
| return nullptr; |
| return EasyUnlockService::Get(profile); |
| } |
| |
| // static |
| void EasyUnlockService::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterDictionaryPref(prefs::kEasyUnlockPairing); |
| proximity_auth::ProximityAuthProfilePrefManager::RegisterPrefs(registry); |
| } |
| |
| class EasyUnlockService::PowerMonitor |
| : public chromeos::PowerManagerClient::Observer { |
| public: |
| explicit PowerMonitor(EasyUnlockService* service) : service_(service) { |
| chromeos::PowerManagerClient::Get()->AddObserver(this); |
| } |
| |
| PowerMonitor(const PowerMonitor&) = delete; |
| PowerMonitor& operator=(const PowerMonitor&) = delete; |
| |
| ~PowerMonitor() override { |
| chromeos::PowerManagerClient::Get()->RemoveObserver(this); |
| } |
| |
| private: |
| // PowerManagerClient::Observer: |
| void SuspendImminent(power_manager::SuspendImminent::Reason reason) override { |
| service_->PrepareForSuspend(); |
| } |
| |
| void SuspendDone(base::TimeDelta sleep_duration) override { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&PowerMonitor::ResetWakingUp, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Seconds(5)); |
| service_->OnSuspendDone(); |
| service_->UpdateAppState(); |
| // Note that `this` may get deleted after `UpdateAppState` is called. |
| } |
| |
| void ResetWakingUp() { service_->UpdateAppState(); } |
| |
| raw_ptr<EasyUnlockService, ExperimentalAsh> service_; |
| base::WeakPtrFactory<PowerMonitor> weak_ptr_factory_{this}; |
| }; |
| |
| EasyUnlockService::EasyUnlockService( |
| Profile* profile, |
| secure_channel::SecureChannelClient* secure_channel_client, |
| device_sync::DeviceSyncClient* device_sync_client, |
| multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client) |
| : EasyUnlockService( |
| profile, |
| secure_channel_client, |
| std::make_unique<EasyUnlockNotificationController>(profile), |
| device_sync_client, |
| multidevice_setup_client) {} |
| |
| EasyUnlockService::EasyUnlockService( |
| Profile* profile, |
| secure_channel::SecureChannelClient* secure_channel_client, |
| std::unique_ptr<EasyUnlockNotificationController> notification_controller, |
| device_sync::DeviceSyncClient* device_sync_client, |
| multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client) |
| : profile_(profile), |
| secure_channel_client_(secure_channel_client), |
| proximity_auth_client_(profile), |
| shut_down_(false), |
| lock_screen_last_shown_timestamp_(base::TimeTicks::Now()), |
| notification_controller_(std::move(notification_controller)), |
| device_sync_client_(device_sync_client), |
| multidevice_setup_client_(multidevice_setup_client) {} |
| |
| EasyUnlockService::~EasyUnlockService() = default; |
| |
| bool EasyUnlockService::AttemptAuth(const AccountId& account_id) { |
| PA_LOG(VERBOSE) << "User began unlock auth attempt."; |
| |
| if (auth_attempt_) { |
| PA_LOG(VERBOSE) << "Already attempting auth, skipping this request."; |
| return false; |
| } |
| |
| if (!GetAccountId().is_valid()) { |
| PA_LOG(ERROR) << "Empty user account. Auth attempt failed."; |
| SmartLockMetricsRecorder::RecordAuthResultUnlockFailure( |
| SmartLockMetricsRecorder::SmartLockAuthResultFailureReason:: |
| kEmptyUserAccount); |
| return false; |
| } |
| |
| if (GetAccountId() != account_id) { |
| SmartLockMetricsRecorder::RecordAuthResultUnlockFailure( |
| SmartLockMetricsRecorder::SmartLockAuthResultFailureReason:: |
| kInvalidAccoundId); |
| |
| PA_LOG(ERROR) << "Check failed: " << GetAccountId().Serialize() << " vs " |
| << account_id.Serialize(); |
| return false; |
| } |
| |
| auth_attempt_ = std::make_unique<EasyUnlockAuthAttempt>(account_id); |
| if (!auth_attempt_->Start()) { |
| SmartLockMetricsRecorder::RecordAuthResultUnlockFailure( |
| SmartLockMetricsRecorder::SmartLockAuthResultFailureReason:: |
| kAuthAttemptCannotStart); |
| auth_attempt_.reset(); |
| return false; |
| } |
| |
| // TODO(tengs): We notify ProximityAuthSystem whenever unlock attempts are |
| // attempted. However, we ideally should refactor the auth attempt logic to |
| // the proximity_auth component. |
| if (!proximity_auth_system_) { |
| PA_LOG(ERROR) << "No ProximityAuthSystem present."; |
| return false; |
| } |
| |
| proximity_auth_system_->OnAuthAttempted(); |
| return true; |
| } |
| |
| void EasyUnlockService::FinalizeUnlock(bool success) { |
| if (!auth_attempt_) { |
| return; |
| } |
| |
| set_will_authenticate_using_easy_unlock(true); |
| auth_attempt_->FinalizeUnlock(GetAccountId(), success); |
| |
| // If successful, allow |auth_attempt_| to continue until |
| // UpdateSmartLockState() is called (indicating screen unlock). |
| |
| // Make sure that the lock screen is updated on failure. |
| if (!success) { |
| auth_attempt_.reset(); |
| RecordEasyUnlockScreenUnlockEvent(EASY_UNLOCK_FAILURE); |
| if (!base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) { |
| HandleAuthFailure(GetAccountId()); |
| } |
| } |
| |
| if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) { |
| NotifySmartLockAuthResult(success); |
| } |
| } |
| |
| AccountId EasyUnlockService::GetAccountId() const { |
| const user_manager::User* const primary_user = |
| user_manager::UserManager::Get()->GetPrimaryUser(); |
| CHECK(primary_user); |
| return primary_user->GetAccountId(); |
| } |
| |
| SmartLockState EasyUnlockService::GetInitialSmartLockState() const { |
| if (IsAllowed() && IsEnabled() && proximity_auth_system_ != nullptr) { |
| return SmartLockState::kConnectingToPhone; |
| } |
| |
| return SmartLockState::kDisabled; |
| } |
| |
| std::string EasyUnlockService::GetLastRemoteStatusUnlockForLogging() { |
| if (proximity_auth_system_) { |
| return proximity_auth_system_->GetLastRemoteStatusUnlockForLogging(); |
| } |
| return std::string(); |
| } |
| |
| proximity_auth::ProximityAuthPrefManager* |
| EasyUnlockService::GetProximityAuthPrefManager() { |
| return pref_manager_.get(); |
| } |
| |
| const multidevice::RemoteDeviceRefList |
| EasyUnlockService::GetRemoteDevicesForTesting() const { |
| if (!proximity_auth_system_) { |
| return multidevice::RemoteDeviceRefList(); |
| } |
| |
| return proximity_auth_system_->GetRemoteDevicesForUser(GetAccountId()); |
| } |
| |
| void EasyUnlockService::HandleAuthFailure(const AccountId& account_id) { |
| NotifySmartLockAuthResult(/*success=*/false); |
| } |
| |
| void EasyUnlockService::Initialize() { |
| proximity_auth::ScreenlockBridge::Get()->AddObserver(this); |
| |
| pref_manager_ = |
| std::make_unique<proximity_auth::ProximityAuthProfilePrefManager>( |
| profile()->GetPrefs(), multidevice_setup_client_); |
| |
| // If `device_sync_client_` is not ready yet, wait for it to call back on |
| // OnReady(). |
| if (device_sync_client_->is_ready()) { |
| OnReady(); |
| } |
| device_sync_client_->AddObserver(this); |
| |
| OnFeatureStatesChanged(multidevice_setup_client_->GetFeatureStates()); |
| multidevice_setup_client_->AddObserver(this); |
| StartFeatureUsageMetrics(); |
| |
| LoadRemoteDevices(); |
| } |
| |
| bool EasyUnlockService::IsAllowed() const { |
| if (shut_down_) { |
| return false; |
| } |
| |
| user_manager::UserManager* user_manager = user_manager::UserManager::Get(); |
| |
| if (!user_manager->IsLoggedInAsUserWithGaiaAccount()) { |
| return false; |
| } |
| |
| // TODO(tengs): Ephemeral accounts generate a new enrollment every time they |
| // are added, so disable Smart Lock to reduce enrollments on server. However, |
| // ephemeral accounts can be locked, so we should revisit this use case. |
| if (user_manager->IsCurrentUserNonCryptohomeDataEphemeral()) { |
| return false; |
| } |
| |
| if (!ProfileHelper::IsPrimaryProfile(profile())) { |
| return false; |
| } |
| |
| if (multidevice_setup_client_->GetFeatureState( |
| multidevice_setup::mojom::Feature::kSmartLock) == |
| multidevice_setup::mojom::FeatureState::kProhibitedByPolicy) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool EasyUnlockService::IsEnabled() const { |
| return multidevice_setup_client_->GetFeatureState( |
| multidevice_setup::mojom::Feature::kSmartLock) == |
| multidevice_setup::mojom::FeatureState::kEnabledByUser; |
| } |
| |
| void EasyUnlockService::UpdateSmartLockState(SmartLockState state) { |
| if (smart_lock_state_ && state == smart_lock_state_.value()) { |
| return; |
| } |
| |
| smart_lock_state_ = state; |
| |
| if (proximity_auth::ScreenlockBridge::Get()->IsLocked()) { |
| auto* lock_handler = |
| proximity_auth::ScreenlockBridge::Get()->lock_handler(); |
| DCHECK(lock_handler); |
| |
| lock_handler->SetSmartLockState(GetAccountId(), state); |
| |
| // TODO(https://crbug.com/1233614): Eventually we would like to remove |
| // auth_type.mojom where AuthType lives, but this will require further |
| // investigation. This logic was copied from legacy |
| // SmartLockStateHandler::UpdateScreenlockAuthType. |
| // Do not override online signin. |
| if (lock_handler->GetAuthType(GetAccountId()) != |
| proximity_auth::mojom::AuthType::ONLINE_SIGN_IN) { |
| if (smart_lock_state_ == SmartLockState::kPhoneAuthenticated) { |
| SetAuthTypeIfChanged( |
| lock_handler, GetAccountId(), |
| proximity_auth::mojom::AuthType::USER_CLICK, |
| l10n_util::GetStringUTF16( |
| IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE)); |
| } else { |
| SetAuthTypeIfChanged(lock_handler, GetAccountId(), |
| proximity_auth::mojom::AuthType::OFFLINE_PASSWORD, |
| std::u16string()); |
| } |
| } |
| } |
| |
| if (state != SmartLockState::kPhoneAuthenticated && auth_attempt_) { |
| // Clean up existing auth attempt if we can no longer authenticate the |
| // remote device. |
| auth_attempt_.reset(); |
| |
| if (!IsSmartLockStateValidOnRemoteAuthFailure()) { |
| HandleAuthFailure(GetAccountId()); |
| } |
| } |
| } |
| |
| void EasyUnlockService::Shutdown() { |
| if (shut_down_) { |
| return; |
| } |
| shut_down_ = true; |
| |
| pref_manager_.reset(); |
| notification_controller_.reset(); |
| |
| device_sync_client_->RemoveObserver(this); |
| |
| multidevice_setup_client_->RemoveObserver(this); |
| |
| StopFeatureUsageMetrics(); |
| |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| |
| proximity_auth::ScreenlockBridge::Get()->RemoveObserver(this); |
| |
| ResetSmartLockState(); |
| proximity_auth_system_.reset(); |
| power_monitor_.reset(); |
| |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void EasyUnlockService::OnScreenDidLock( |
| proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) { |
| if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) { |
| ShowInitialSmartLockState(); |
| } |
| |
| set_will_authenticate_using_easy_unlock(false); |
| lock_screen_last_shown_timestamp_ = base::TimeTicks::Now(); |
| } |
| |
| void EasyUnlockService::OnScreenDidUnlock( |
| proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) { |
| // If we tried to load remote devices (e.g. after a sync or the |
| // service was initialized) while the screen was locked, we can now |
| // load the new remote devices. |
| // |
| // It's important to go through this code path even if unlocking the |
| // login screen. Because when the service is initialized while the |
| // user is signing in we need to load the remotes. Otherwise, the |
| // first time the user locks the screen the feature won't work. |
| if (deferring_device_load_) { |
| PA_LOG(VERBOSE) << "Loading deferred devices after screen unlock."; |
| deferring_device_load_ = false; |
| LoadRemoteDevices(); |
| } |
| |
| // Do not process events for the login screen. |
| if (screen_type != |
| proximity_auth::ScreenlockBridge::LockHandler::LOCK_SCREEN) { |
| return; |
| } |
| |
| if (shown_pairing_changed_notification_) { |
| shown_pairing_changed_notification_ = false; |
| |
| if (!GetUnlockKeys().empty()) { |
| notification_controller_->ShowPairingChangeAppliedNotification( |
| GetUnlockKeys()[0].name()); |
| } |
| } |
| |
| // Only record metrics for users who have enabled the feature. |
| if (IsEnabled()) { |
| EasyUnlockAuthEvent event = will_authenticate_using_easy_unlock() |
| ? EASY_UNLOCK_SUCCESS |
| : GetPasswordAuthEvent(); |
| RecordEasyUnlockScreenUnlockEvent(event); |
| |
| if (will_authenticate_using_easy_unlock()) { |
| // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric. |
| SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice( |
| SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kSmartLock); |
| SmartLockMetricsRecorder::RecordAuthResultUnlockSuccess(); |
| RecordEasyUnlockScreenUnlockDuration(base::TimeTicks::Now() - |
| lock_screen_last_shown_timestamp_); |
| } else { |
| SmartLockMetricsRecorder::RecordAuthMethodChoiceUnlockPasswordState( |
| GetSmartUnlockPasswordAuthEvent()); |
| // TODO(crbug.com/1171972): Deprecate the AuthMethodChoice metric. |
| SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice( |
| SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kOther); |
| OnUserEnteredPassword(); |
| } |
| } |
| |
| set_will_authenticate_using_easy_unlock(false); |
| } |
| |
| void EasyUnlockService::OnFocusedUserChanged(const AccountId& account_id) { |
| // Nothing to do. |
| } |
| |
| void EasyUnlockService::OnReady() { |
| // If the local device and synced devices are ready for the first time, |
| // establish what the unlock keys were before the next sync. This is necessary |
| // in order for OnNewDevicesSynced() to determine if new devices were added |
| // since the last sync. |
| remote_device_unlock_keys_before_sync_ = GetUnlockKeys(); |
| } |
| |
| void EasyUnlockService::OnEnrollmentFinished() { |
| // The local device may be ready for the first time, or it may have been |
| // updated, so reload devices. |
| LoadRemoteDevices(); |
| } |
| |
| void EasyUnlockService::OnNewDevicesSynced() { |
| std::set<std::string> public_keys_before_sync; |
| for (const auto& remote_device : remote_device_unlock_keys_before_sync_) { |
| public_keys_before_sync.insert(remote_device.public_key()); |
| } |
| |
| multidevice::RemoteDeviceRefList remote_device_unlock_keys_after_sync = |
| GetUnlockKeys(); |
| std::set<std::string> public_keys_after_sync; |
| for (const auto& remote_device : remote_device_unlock_keys_after_sync) { |
| public_keys_after_sync.insert(remote_device.public_key()); |
| } |
| |
| ShowNotificationIfNewDevicePresent(public_keys_before_sync, |
| public_keys_after_sync); |
| |
| LoadRemoteDevices(); |
| |
| remote_device_unlock_keys_before_sync_ = remote_device_unlock_keys_after_sync; |
| } |
| |
| void EasyUnlockService::OnFeatureStatesChanged( |
| const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap& |
| feature_states_map) { |
| LoadRemoteDevices(); |
| UpdateAppState(); |
| } |
| |
| EasyUnlockAuthEvent EasyUnlockService::GetPasswordAuthEvent() const { |
| DCHECK(IsEnabled()); |
| |
| if (!smart_lock_state_) { |
| return PASSWORD_ENTRY_NO_SMARTLOCK_STATE_HANDLER; |
| } |
| |
| SmartLockState state = smart_lock_state_.value(); |
| |
| switch (state) { |
| case SmartLockState::kInactive: |
| case SmartLockState::kDisabled: |
| return PASSWORD_ENTRY_SERVICE_NOT_ACTIVE; |
| case SmartLockState::kBluetoothDisabled: |
| return PASSWORD_ENTRY_NO_BLUETOOTH; |
| case SmartLockState::kConnectingToPhone: |
| return PASSWORD_ENTRY_BLUETOOTH_CONNECTING; |
| case SmartLockState::kPhoneNotFound: |
| return PASSWORD_ENTRY_NO_PHONE; |
| case SmartLockState::kPhoneNotAuthenticated: |
| return PASSWORD_ENTRY_PHONE_NOT_AUTHENTICATED; |
| case SmartLockState::kPhoneFoundLockedAndProximate: |
| return PASSWORD_ENTRY_PHONE_LOCKED; |
| case SmartLockState::kPhoneNotLockable: |
| return PASSWORD_ENTRY_PHONE_NOT_LOCKABLE; |
| case SmartLockState::kPhoneFoundUnlockedAndDistant: |
| return PASSWORD_ENTRY_RSSI_TOO_LOW; |
| case SmartLockState::kPhoneFoundLockedAndDistant: |
| return PASSWORD_ENTRY_PHONE_LOCKED_AND_RSSI_TOO_LOW; |
| case SmartLockState::kPhoneAuthenticated: |
| return PASSWORD_ENTRY_WITH_AUTHENTICATED_PHONE; |
| case SmartLockState::kPrimaryUserAbsent: |
| return PASSWORD_ENTRY_PRIMARY_USER_ABSENT; |
| } |
| |
| NOTREACHED(); |
| return EASY_UNLOCK_AUTH_EVENT_COUNT; |
| } |
| |
| SmartLockMetricsRecorder::SmartLockAuthEventPasswordState |
| EasyUnlockService::GetSmartUnlockPasswordAuthEvent() const { |
| DCHECK(IsEnabled()); |
| |
| if (!smart_lock_state_) { |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kUnknownState; |
| } |
| |
| SmartLockState state = smart_lock_state_.value(); |
| |
| switch (state) { |
| case SmartLockState::kInactive: |
| case SmartLockState::kDisabled: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kServiceNotActive; |
| case SmartLockState::kBluetoothDisabled: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kNoBluetooth; |
| case SmartLockState::kConnectingToPhone: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kBluetoothConnecting; |
| case SmartLockState::kPhoneNotFound: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kCouldNotConnectToPhone; |
| case SmartLockState::kPhoneNotAuthenticated: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kNotAuthenticated; |
| case SmartLockState::kPhoneFoundLockedAndProximate: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kPhoneLocked; |
| case SmartLockState::kPhoneFoundUnlockedAndDistant: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kRssiTooLow; |
| case SmartLockState::kPhoneFoundLockedAndDistant: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kPhoneLockedAndRssiTooLow; |
| case SmartLockState::kPhoneAuthenticated: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kAuthenticatedPhone; |
| case SmartLockState::kPhoneNotLockable: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kPhoneNotLockable; |
| case SmartLockState::kPrimaryUserAbsent: |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kPrimaryUserAbsent; |
| default: |
| NOTREACHED(); |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kUnknownState; |
| } |
| |
| NOTREACHED(); |
| return SmartLockMetricsRecorder::SmartLockAuthEventPasswordState:: |
| kUnknownState; |
| } |
| |
| multidevice::RemoteDeviceRefList EasyUnlockService::GetUnlockKeys() { |
| multidevice::RemoteDeviceRefList unlock_keys; |
| for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) { |
| bool unlock_key = remote_device.GetSoftwareFeatureState( |
| multidevice::SoftwareFeature::kSmartLockHost) == |
| multidevice::SoftwareFeatureState::kEnabled; |
| if (unlock_key) { |
| unlock_keys.push_back(remote_device); |
| } |
| } |
| return unlock_keys; |
| } |
| |
| bool EasyUnlockService::IsSmartLockStateValidOnRemoteAuthFailure() const { |
| // Note that NO_PHONE is not valid in this case because the phone may close |
| // the connection if the auth challenge sent to it is invalid. This case |
| // should be handled as authentication failure. |
| return smart_lock_state_ == SmartLockState::kInactive || |
| smart_lock_state_ == SmartLockState::kDisabled || |
| smart_lock_state_ == SmartLockState::kBluetoothDisabled || |
| smart_lock_state_ == SmartLockState::kPhoneFoundLockedAndProximate; |
| } |
| |
| // TODO(jhawkins): This method with `has_unlock_keys` == true is the only signal |
| // that SmartLock setup has completed successfully. Make this signal more |
| // explicit. |
| void EasyUnlockService::LoadRemoteDevices() { |
| if (!device_sync_client_->is_ready()) { |
| // OnEnrollmentFinished() or OnNewDevicesSynced() will call back on this |
| // method once `device_sync_client_` is ready. |
| PA_LOG(VERBOSE) << "DeviceSyncClient is not ready yet, delaying " |
| "UseLoadedRemoteDevices()."; |
| return; |
| } |
| |
| if (!IsEnabled()) { |
| // OnFeatureStatesChanged() will call back on this method when feature state |
| // changes. |
| PA_LOG(VERBOSE) << "Smart Lock is not enabled by user; aborting."; |
| SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(), |
| absl::nullopt /* local_device */); |
| return; |
| } |
| |
| bool has_unlock_keys = !GetUnlockKeys().empty(); |
| |
| // TODO(jhawkins): The enabled pref should not be tied to whether unlock keys |
| // exist; instead, both of these variables should be used to determine |
| // IsEnabled(). |
| pref_manager_->SetIsEasyUnlockEnabled(has_unlock_keys); |
| if (has_unlock_keys) { |
| // If `has_unlock_keys` is true, then the user must have successfully |
| // completed setup. Track that the IsEasyUnlockEnabled pref is actively set |
| // by the user, as opposed to passively being set to disabled (the default |
| // state). |
| pref_manager_->SetEasyUnlockEnabledStateSet(); |
| LogSmartLockEnabledState(SmartLockEnabledState::ENABLED); |
| } else { |
| PA_LOG(ERROR) << "Smart Lock is enabled by user, but no unlock key is " |
| "present; aborting."; |
| SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(), |
| absl::nullopt /* local_device */); |
| |
| if (pref_manager_->IsEasyUnlockEnabledStateSet()) { |
| LogSmartLockEnabledState(SmartLockEnabledState::DISABLED); |
| } else { |
| LogSmartLockEnabledState(SmartLockEnabledState::UNSET); |
| } |
| return; |
| } |
| |
| // This code path may be hit by: |
| // 1. New devices were synced on the lock screen. |
| // 2. The service was initialized while the login screen is still up. |
| if (proximity_auth::ScreenlockBridge::Get()->IsLocked()) { |
| PA_LOG(VERBOSE) << "Deferring device load until screen is unlocked."; |
| deferring_device_load_ = true; |
| return; |
| } |
| |
| UseLoadedRemoteDevices(GetUnlockKeys()); |
| } |
| |
| void EasyUnlockService::NotifySmartLockAuthResult(bool success) { |
| if (!proximity_auth::ScreenlockBridge::Get()->IsLocked()) { |
| return; |
| } |
| |
| proximity_auth::ScreenlockBridge::Get() |
| ->lock_handler() |
| ->NotifySmartLockAuthResult(GetAccountId(), success); |
| } |
| |
| void EasyUnlockService::OnSuspendDone() { |
| if (proximity_auth_system_) { |
| proximity_auth_system_->OnSuspendDone(); |
| } |
| } |
| |
| void EasyUnlockService::OnUserEnteredPassword() { |
| if (proximity_auth_system_) { |
| proximity_auth_system_->CancelConnectionAttempt(); |
| } |
| } |
| |
| void EasyUnlockService::PrepareForSuspend() { |
| if (smart_lock_state_ && *smart_lock_state_ != SmartLockState::kInactive) { |
| ShowInitialSmartLockState(); |
| } |
| |
| if (proximity_auth_system_) { |
| proximity_auth_system_->OnSuspend(); |
| } |
| } |
| |
| void EasyUnlockService::ResetSmartLockState() { |
| smart_lock_state_.reset(); |
| auth_attempt_.reset(); |
| } |
| |
| void EasyUnlockService::SetProximityAuthDevices( |
| const AccountId& account_id, |
| const multidevice::RemoteDeviceRefList& remote_devices, |
| absl::optional<multidevice::RemoteDeviceRef> local_device) { |
| UMA_HISTOGRAM_COUNTS_100("SmartLock.EnabledDevicesCount", |
| remote_devices.size()); |
| |
| if (remote_devices.size() == 0) { |
| proximity_auth_system_.reset(); |
| return; |
| } |
| |
| if (!proximity_auth_system_) { |
| PA_LOG(VERBOSE) << "Creating ProximityAuthSystem."; |
| proximity_auth_system_ = |
| std::make_unique<proximity_auth::ProximityAuthSystem>( |
| proximity_auth_client(), secure_channel_client_); |
| } |
| |
| proximity_auth_system_->SetRemoteDevicesForUser(account_id, remote_devices, |
| local_device); |
| proximity_auth_system_->Start(); |
| } |
| |
| void EasyUnlockService::ShowChromebookAddedNotification() { |
| // The user may have decided to disable Smart Lock or the whole multidevice |
| // suite immediately after completing setup, so ensure that Smart Lock is |
| // enabled. |
| if (IsEnabled()) { |
| notification_controller_->ShowChromebookAddedNotification(); |
| } |
| } |
| |
| void EasyUnlockService::ShowInitialSmartLockState() { |
| // Only proceed if the screen is locked to prevent the UI event from not |
| // persisting within UpdateSmartLockState(). |
| // |
| // Note: ScreenlockBridge::IsLocked() may return a false positive if the |
| // system is "warming up". To work around this race, |
| // ShowInitialSmartLockState() is also called from OnScreenDidLock() (which |
| // triggers when ScreenlockBridge::IsLocked() becomes true) to ensure that |
| // an initial state is displayed in the UI. |
| // TODO(b/227674947) Investigate whether a false positive is still possible |
| // now that Sign in with Smart Lock is deprecated. |
| auto* screenlock_bridge = proximity_auth::ScreenlockBridge::Get(); |
| if (screenlock_bridge && screenlock_bridge->IsLocked()) { |
| UpdateSmartLockState(GetInitialSmartLockState()); |
| } |
| } |
| |
| void EasyUnlockService::ShowNotificationIfNewDevicePresent( |
| const std::set<std::string>& public_keys_before_sync, |
| const std::set<std::string>& public_keys_after_sync) { |
| if (public_keys_before_sync == public_keys_after_sync) { |
| return; |
| } |
| |
| // Show the appropriate notification if an unlock key is first synced or if it |
| // changes an existing key. |
| // Note: We do not show a notification when EasyUnlock is disabled by sync nor |
| // if EasyUnlock was enabled through the setup app. |
| if (!public_keys_after_sync.empty()) { |
| if (public_keys_before_sync.empty()) { |
| multidevice_setup::MultiDeviceSetupDialog* multidevice_setup_dialog = |
| multidevice_setup::MultiDeviceSetupDialog::Get(); |
| if (multidevice_setup_dialog) { |
| // Delay showing the "Chromebook added" notification until the |
| // MultiDeviceSetupDialog is closed. |
| multidevice_setup_dialog->AddOnCloseCallback( |
| base::BindOnce(&EasyUnlockService::ShowChromebookAddedNotification, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| notification_controller_->ShowChromebookAddedNotification(); |
| } else { |
| shown_pairing_changed_notification_ = true; |
| notification_controller_->ShowPairingChangeNotification(); |
| } |
| } |
| } |
| |
| void EasyUnlockService::StartFeatureUsageMetrics() { |
| feature_usage_metrics_ = |
| std::make_unique<SmartLockFeatureUsageMetrics>(multidevice_setup_client_); |
| |
| SmartLockMetricsRecorder::SetUsageRecorderInstance( |
| feature_usage_metrics_.get()); |
| } |
| |
| void EasyUnlockService::StopFeatureUsageMetrics() { |
| feature_usage_metrics_.reset(); |
| SmartLockMetricsRecorder::SetUsageRecorderInstance(nullptr); |
| } |
| |
| void EasyUnlockService::UpdateAppState() { |
| if (IsAllowed()) { |
| if (proximity_auth_system_) { |
| proximity_auth_system_->Start(); |
| } |
| |
| if (!power_monitor_) { |
| power_monitor_ = std::make_unique<PowerMonitor>(this); |
| } |
| } |
| } |
| |
| void EasyUnlockService::UseLoadedRemoteDevices( |
| const multidevice::RemoteDeviceRefList& remote_devices) { |
| // When EasyUnlock is enabled, only one EasyUnlock host should exist. |
| if (remote_devices.size() != 1u) { |
| PA_LOG(ERROR) << "There should only be 1 Smart Lock host, but there are: " |
| << remote_devices.size(); |
| SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(), |
| absl::nullopt); |
| NOTREACHED(); |
| return; |
| } |
| |
| absl::optional<multidevice::RemoteDeviceRef> local_device = |
| device_sync_client_->GetLocalDeviceMetadata(); |
| if (!local_device) { |
| PA_LOG(ERROR) << "EasyUnlockService::" << __func__ |
| << ": Local device unexpectedly null."; |
| SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(), |
| absl::nullopt); |
| return; |
| } |
| |
| SetProximityAuthDevices(GetAccountId(), remote_devices, local_device); |
| } |
| |
| } // namespace ash |