Index: components/proximity_auth/unlock_manager.cc |
diff --git a/components/proximity_auth/unlock_manager.cc b/components/proximity_auth/unlock_manager.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e778042e3ea4ef15692600459b4530fba8d7114d |
--- /dev/null |
+++ b/components/proximity_auth/unlock_manager.cc |
@@ -0,0 +1,465 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/proximity_auth/unlock_manager.h" |
+ |
+#include "base/bind.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/thread_task_runner_handle.h" |
+#include "base/time/time.h" |
+#include "components/proximity_auth/client.h" |
+#include "components/proximity_auth/logging/logging.h" |
+#include "components/proximity_auth/metrics.h" |
+#include "components/proximity_auth/proximity_auth_client.h" |
+#include "components/proximity_auth/proximity_monitor.h" |
+#include "device/bluetooth/bluetooth_adapter_factory.h" |
+ |
+#if defined(OS_CHROMEOS) |
+#include "chromeos/dbus/dbus_thread_manager.h" |
+#endif // defined(OS_CHROMEOS) |
+ |
+namespace proximity_auth { |
+namespace { |
+ |
+// The maximum amount of time, in seconds, that the unlock manager can stay in |
+// the 'waking up' state after resuming from sleep. |
+const int kWakingUpDurationSecs = 5; |
+ |
+// The limit, in seconds, on the elapsed time for an auth attempt. If an auth |
+// attempt exceeds this limit, it will time out and be rejected. This is |
+// provided as a failsafe, in case something goes wrong. |
+const int kAuthAttemptTimeoutSecs = 5; |
+ |
+// Returns the remote device's security settings state, for metrics, |
+// corresponding to a remote status update. |
+metrics::RemoteSecuritySettingsState GetRemoteSecuritySettingsState( |
+ const RemoteStatusUpdate& status_update) { |
+ switch (status_update.secure_screen_lock_state) { |
+ case SECURE_SCREEN_LOCK_STATE_UNKNOWN: |
+ return metrics::RemoteSecuritySettingsState::UNKNOWN; |
+ |
+ case SECURE_SCREEN_LOCK_DISABLED: |
+ switch (status_update.trust_agent_state) { |
+ case TRUST_AGENT_UNSUPPORTED: |
+ return metrics::RemoteSecuritySettingsState:: |
+ SCREEN_LOCK_DISABLED_TRUST_AGENT_UNSUPPORTED; |
+ case TRUST_AGENT_DISABLED: |
+ return metrics::RemoteSecuritySettingsState:: |
+ SCREEN_LOCK_DISABLED_TRUST_AGENT_DISABLED; |
+ case TRUST_AGENT_ENABLED: |
+ return metrics::RemoteSecuritySettingsState:: |
+ SCREEN_LOCK_DISABLED_TRUST_AGENT_ENABLED; |
+ } |
+ |
+ case SECURE_SCREEN_LOCK_ENABLED: |
+ switch (status_update.trust_agent_state) { |
+ case TRUST_AGENT_UNSUPPORTED: |
+ return metrics::RemoteSecuritySettingsState:: |
+ SCREEN_LOCK_ENABLED_TRUST_AGENT_UNSUPPORTED; |
+ case TRUST_AGENT_DISABLED: |
+ return metrics::RemoteSecuritySettingsState:: |
+ SCREEN_LOCK_ENABLED_TRUST_AGENT_DISABLED; |
+ case TRUST_AGENT_ENABLED: |
+ return metrics::RemoteSecuritySettingsState:: |
+ SCREEN_LOCK_ENABLED_TRUST_AGENT_ENABLED; |
+ } |
+ } |
+ |
+ NOTREACHED(); |
+ return metrics::RemoteSecuritySettingsState::UNKNOWN; |
+} |
+ |
+} // namespace |
+ |
+UnlockManager::UnlockManager(ScreenlockType screenlock_type, |
+ scoped_ptr<ProximityMonitor> proximity_monitor, |
+ ProximityAuthClient* proximity_auth_client) |
+ : screenlock_type_(screenlock_type), |
+ controller_(nullptr), |
+ client_(nullptr), |
+ proximity_monitor_(proximity_monitor.Pass()), |
+ proximity_auth_client_(proximity_auth_client), |
+ is_locked_(false), |
+ is_attempting_auth_(false), |
+ is_waking_up_(false), |
+ screenlock_state_(ScreenlockState::INACTIVE), |
+ clear_waking_up_state_weak_ptr_factory_(this), |
+ reject_auth_attempt_weak_ptr_factory_(this), |
+ weak_ptr_factory_(this) { |
+ // TODO(isherman): Register for auth attempt notifications, equivalent to the |
+ // JavaScript lines: |
+ // |
+ // chrome.screenlockPrivate.onAuthAttempted.addListener( |
+ // this.onAuthAttempted_.bind(this)); |
+ |
+ ScreenlockBridge* screenlock_bridge = ScreenlockBridge::Get(); |
+ screenlock_bridge->AddObserver(this); |
+ OnScreenLockStateChanged(screenlock_bridge->IsLocked()); |
+ |
+#if defined(OS_CHROMEOS) |
+ DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); |
+#endif // defined(OS_CHROMEOS) |
+ SetWakingUpState(true); |
+ |
+ if (device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) { |
+ device::BluetoothAdapterFactory::GetAdapter( |
+ base::Bind(&UnlockManager::OnBluetoothAdapterInitialized, |
+ weak_ptr_factory_.GetWeakPtr())); |
+ } |
+} |
+ |
+UnlockManager::~UnlockManager() { |
+ if (client_) |
+ client_->RemoveObserver(this); |
+ |
+ ScreenlockBridge::Get()->RemoveObserver(this); |
+ |
+#if defined(OS_CHROMEOS) |
+ DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this); |
+#endif // defined(OS_CHROMEOS) |
+ |
+ if (bluetooth_adapter_) |
+ bluetooth_adapter_->RemoveObserver(this); |
+} |
+ |
+bool UnlockManager::IsUnlockAllowed() { |
+ return (remote_screenlock_state_ && |
+ *remote_screenlock_state_ == RemoteScreenlockState::UNLOCKED && |
+ controller_ && |
+ controller_->GetState() == |
+ Controller::State::SECURE_CHANNEL_ESTABLISHED && |
+ proximity_monitor_->IsUnlockAllowed() && |
+ (screenlock_type_ != ScreenlockType::SIGN_IN || |
+ (client_ && client_->SupportsSignIn()))); |
+} |
+ |
+void UnlockManager::SetController(Controller* controller) { |
+ if (client_) { |
+ client_->RemoveObserver(this); |
+ client_ = nullptr; |
+ } |
+ |
+ controller_ = controller; |
+ if (controller_) |
+ SetWakingUpState(true); |
+ |
+ UpdateLockScreen(); |
+} |
+ |
+void UnlockManager::OnControllerStateChanged() { |
+ Controller::State state = controller_->GetState(); |
+ PA_LOG(INFO) << "[Unlock] Controller state changed: " |
+ << static_cast<int>(state); |
+ |
+ remote_screenlock_state_.reset(); |
+ if (state == Controller::State::SECURE_CHANNEL_ESTABLISHED) { |
+ client_ = controller_->GetClient(); |
+ client_->AddObserver(this); |
+ } |
+ |
+ if (state == Controller::State::AUTHENTICATION_FAILED) |
+ SetWakingUpState(false); |
+ |
+ UpdateLockScreen(); |
+} |
+ |
+void UnlockManager::OnUnlockEventSent(bool success) { |
+ if (!is_attempting_auth_) { |
+ PA_LOG(ERROR) << "[Unlock] Sent easy_unlock event, but no auth attempted."; |
+ return; |
+ } |
+ |
+ if (sign_in_secret_ && success) |
+ proximity_auth_client_->FinalizeSignin(*sign_in_secret_); |
Tim Song
2015/07/23 21:32:15
Shouldn't you move these two lines inside AcceptAu
Ilya Sherman
2015/08/11 23:37:15
The current flow matches what the app is doing (se
|
+ |
+ AcceptAuthAttempt(success); |
+} |
+ |
+void UnlockManager::OnRemoteStatusUpdate( |
+ const RemoteStatusUpdate& status_update) { |
+ PA_LOG(INFO) << "[Unlock] Status Update: (" |
+ << "user_present=" << status_update.user_presence << ", " |
+ << "secure_screen_lock=" |
+ << status_update.secure_screen_lock_state << ", " |
+ << "trust_agent=" << status_update.trust_agent_state << ")"; |
+ metrics::RecordRemoteSecuritySettingsState( |
+ GetRemoteSecuritySettingsState(status_update)); |
+ |
+ remote_screenlock_state_.reset(new RemoteScreenlockState( |
+ GetScreenlockStateFromRemoteUpdate(status_update))); |
+ |
+ // This also calls |UpdateLockScreen()| |
+ SetWakingUpState(false); |
+} |
+ |
+void UnlockManager::OnDecryptResponse(scoped_ptr<std::string> decrypted_bytes) { |
+ if (!is_attempting_auth_) { |
+ PA_LOG(ERROR) << "[Unlock] Decrypt response received but not attempting " |
+ << "auth."; |
+ return; |
+ } |
+ |
+ if (!decrypted_bytes) { |
+ PA_LOG(INFO) << "[Unlock] Failed to decrypt sign-in challenge."; |
+ AcceptAuthAttempt(false); |
+ } else { |
+ sign_in_secret_ = decrypted_bytes.Pass(); |
+ client_->DispatchUnlockEvent(); |
+ } |
+} |
+ |
+void UnlockManager::OnUnlockResponse(bool success) { |
+ if (!is_attempting_auth_) { |
+ PA_LOG(ERROR) << "[Unlock] Unlock response received but not attempting " |
+ << "auth."; |
+ return; |
+ } |
+ |
+ PA_LOG(INFO) << "[Unlock] Unlock response from remote device: " |
+ << (success ? "success" : "failure"); |
+ if (success) |
+ client_->DispatchUnlockEvent(); |
+ else |
+ AcceptAuthAttempt(false); |
+} |
+ |
+void UnlockManager::OnDisconnected() { |
+ client_->RemoveObserver(this); |
+ client_ = nullptr; |
+} |
+ |
+void UnlockManager::OnScreenDidLock( |
+ ScreenlockBridge::LockHandler::ScreenType screen_type) { |
+ OnScreenLockStateChanged(true); |
+} |
+ |
+void UnlockManager::OnScreenDidUnlock( |
+ ScreenlockBridge::LockHandler::ScreenType screen_type) { |
+ OnScreenLockStateChanged(false); |
+} |
+ |
+void UnlockManager::OnFocusedUserChanged(const std::string& user_id) {} |
+ |
+void UnlockManager::OnScreenLockStateChanged(bool is_locked) { |
Tim Song
2015/07/23 21:32:15
There is already a |ScreenlockState| enum that has
Ilya Sherman
2015/08/11 23:37:15
Done.
|
+ // TODO(tengs): Chrome will only start connecting to the phone when |
+ // the screen is locked, for privacy reasons. We should reinvestigate |
+ // this behaviour if we want automatic locking. |
+ if (is_locked && bluetooth_adapter_ && bluetooth_adapter_->IsPowered() && |
+ controller_ && |
+ controller_->GetState() == Controller::State::FINDING_CONNECTION) { |
+ SetWakingUpState(true); |
+ } |
+ |
+ is_locked_ = is_locked; |
+ UpdateProximityMonitorState(); |
+} |
+ |
+void UnlockManager::OnBluetoothAdapterInitialized( |
+ scoped_refptr<device::BluetoothAdapter> adapter) { |
+ bluetooth_adapter_ = adapter; |
+ bluetooth_adapter_->AddObserver(this); |
+} |
+ |
+void UnlockManager::AdapterPresentChanged(device::BluetoothAdapter* adapter, |
+ bool present) { |
+ UpdateLockScreen(); |
+} |
+ |
+void UnlockManager::AdapterPoweredChanged(device::BluetoothAdapter* adapter, |
+ bool powered) { |
+ UpdateLockScreen(); |
+} |
+ |
+#if defined(OS_CHROMEOS) |
+void UnlockManager::SuspendDone(const base::TimeDelta& sleep_duration) { |
+ SetWakingUpState(true); |
+} |
+#endif // defined(OS_CHROMEOS) |
+ |
+void UnlockManager::OnAuthAttempted( |
+ ScreenlockBridge::LockHandler::AuthType auth_type) { |
+ if (is_attempting_auth_) { |
+ PA_LOG(INFO) << "[Unlock] Already attempting auth."; |
+ return; |
+ } |
+ |
+ if (auth_type != ScreenlockBridge::LockHandler::USER_CLICK) |
+ return; |
+ |
+ is_attempting_auth_ = true; |
+ |
+ if (!controller_) { |
+ PA_LOG(ERROR) << "[Unlock] No controller active when auth is attempted"; |
+ AcceptAuthAttempt(false); |
+ UpdateLockScreen(); |
+ return; |
+ } |
+ |
+ if (!IsUnlockAllowed()) { |
+ AcceptAuthAttempt(false); |
+ UpdateLockScreen(); |
+ return; |
+ } |
+ |
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&UnlockManager::AcceptAuthAttempt, |
+ reject_auth_attempt_weak_ptr_factory_.GetWeakPtr(), false), |
+ base::TimeDelta::FromSeconds(kAuthAttemptTimeoutSecs)); |
+ |
+ if (screenlock_type_ == ScreenlockType::SIGN_IN) { |
+ SendSignInChallenge(); |
+ } else { |
+ if (client_->SupportsSignIn()) { |
+ client_->RequestUnlock(); |
+ } else { |
+ PA_LOG(INFO) << "[Unlock] Protocol v3.1 not supported, skipping " |
+ << "request_unlock."; |
+ client_->DispatchUnlockEvent(); |
+ } |
+ } |
+} |
+ |
+void UnlockManager::SendSignInChallenge() { |
+ // TODO(isherman): Implement. |
+ NOTIMPLEMENTED(); |
+} |
+ |
+ScreenlockState UnlockManager::GetScreenlockState() { |
+ if (!controller_ || controller_->GetState() == Controller::State::STOPPED) |
+ return ScreenlockState::INACTIVE; |
+ |
+ if (IsUnlockAllowed()) |
+ return ScreenlockState::AUTHENTICATED; |
+ |
+ if (controller_->GetState() == Controller::State::AUTHENTICATION_FAILED) |
+ return ScreenlockState::PHONE_NOT_AUTHENTICATED; |
+ |
+ if (is_waking_up_) |
+ return ScreenlockState::BLUETOOTH_CONNECTING; |
+ |
+ if (!bluetooth_adapter_ || !bluetooth_adapter_->IsPowered()) |
+ return ScreenlockState::NO_BLUETOOTH; |
+ |
+ if (screenlock_type_ == ScreenlockType::SIGN_IN && client_ && |
+ !client_->SupportsSignIn()) |
+ return ScreenlockState::PHONE_UNSUPPORTED; |
+ |
+ // If the RSSI is too low, then the remote device is nowhere near the local |
+ // device. This message should take priority over messages about screen lock |
+ // states. |
+ if (!proximity_monitor_->IsUnlockAllowed() && |
+ !proximity_monitor_->IsInRssiRange()) |
+ return ScreenlockState::RSSI_TOO_LOW; |
+ |
+ if (remote_screenlock_state_) { |
+ switch (*remote_screenlock_state_) { |
+ case RemoteScreenlockState::DISABLED: |
+ return ScreenlockState::PHONE_NOT_LOCKABLE; |
+ |
+ case RemoteScreenlockState::LOCKED: |
+ if (proximity_monitor_->GetStrategy() == |
+ ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER && |
+ !proximity_monitor_->IsUnlockAllowed()) { |
+ return ScreenlockState::PHONE_LOCKED_AND_TX_POWER_TOO_HIGH; |
+ } |
+ return ScreenlockState::PHONE_LOCKED; |
+ |
+ case RemoteScreenlockState::UNKNOWN: |
+ return ScreenlockState::PHONE_UNSUPPORTED; |
+ |
+ case RemoteScreenlockState::UNLOCKED: |
+ // Handled by the code below. |
+ break; |
+ } |
+ } |
+ |
+ if (!proximity_monitor_->IsUnlockAllowed()) { |
+ ProximityMonitor::Strategy strategy = proximity_monitor_->GetStrategy(); |
+ if (strategy != ProximityMonitor::Strategy::CHECK_TRANSMIT_POWER) { |
+ // CHECK_RSSI should have been handled above, and no other states should |
+ // prevent unlocking. |
+ PA_LOG(ERROR) << "[Unlock] Invalid ProximityMonitor strategy: " |
+ << static_cast<int>(strategy); |
+ return ScreenlockState::NO_PHONE; |
+ } |
+ return ScreenlockState::TX_POWER_TOO_HIGH; |
+ } |
+ |
+ return ScreenlockState::NO_PHONE; |
+} |
+ |
+void UnlockManager::UpdateLockScreen() { |
+ UpdateProximityMonitorState(); |
+ |
+ ScreenlockState new_state = GetScreenlockState(); |
+ if (screenlock_state_ == new_state) |
+ return; |
+ |
+ proximity_auth_client_->UpdateScreenlockState(new_state); |
+ screenlock_state_ = new_state; |
+} |
+ |
+void UnlockManager::UpdateProximityMonitorState() { |
+ if (is_locked_ && controller_ && |
+ controller_->GetState() == |
+ Controller::State::SECURE_CHANNEL_ESTABLISHED) { |
+ proximity_monitor_->Start(); |
+ } else { |
+ proximity_monitor_->Stop(); |
+ } |
+} |
+ |
+void UnlockManager::SetWakingUpState(bool is_waking_up) { |
+ is_waking_up_ = is_waking_up; |
+ |
+ // Clear the waking up state after a timeout. |
+ clear_waking_up_state_weak_ptr_factory_.InvalidateWeakPtrs(); |
+ if (is_waking_up_) { |
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
+ FROM_HERE, |
+ base::Bind(&UnlockManager::SetWakingUpState, |
+ clear_waking_up_state_weak_ptr_factory_.GetWeakPtr(), false), |
+ base::TimeDelta::FromSeconds(kWakingUpDurationSecs)); |
+ } |
+ |
+ UpdateLockScreen(); |
+} |
+ |
+void UnlockManager::AcceptAuthAttempt(bool should_accept) { |
+ if (!is_attempting_auth_) |
+ return; |
+ |
+ // Cancel the pending task to time out the auth attempt. |
+ reject_auth_attempt_weak_ptr_factory_.InvalidateWeakPtrs(); |
+ |
+ if (should_accept) |
+ proximity_monitor_->RecordProximityMetricsOnAuthSuccess(); |
+ |
+ is_attempting_auth_ = false; |
+ proximity_auth_client_->FinalizeUnlock(should_accept); |
+} |
+ |
+UnlockManager::RemoteScreenlockState |
+UnlockManager::GetScreenlockStateFromRemoteUpdate(RemoteStatusUpdate update) { |
+ switch (update.secure_screen_lock_state) { |
+ case SECURE_SCREEN_LOCK_DISABLED: |
+ return RemoteScreenlockState::DISABLED; |
+ |
+ case SECURE_SCREEN_LOCK_ENABLED: |
+ if (update.user_presence == USER_PRESENT) |
+ return RemoteScreenlockState::UNLOCKED; |
+ |
+ return RemoteScreenlockState::LOCKED; |
+ |
+ case SECURE_SCREEN_LOCK_STATE_UNKNOWN: |
+ return RemoteScreenlockState::UNKNOWN; |
+ } |
+ |
+ NOTREACHED(); |
+ return RemoteScreenlockState::UNKNOWN; |
+} |
+ |
+} // namespace proximity_auth |