blob: 4e09a32a81763e09358f5cfedd6b2d7687b1ea06 [file] [log] [blame]
// 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