summaryrefslogtreecommitdiffstats
path: root/components/proximity_auth
diff options
context:
space:
mode:
authorisherman <isherman@chromium.org>2015-08-14 19:47:59 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-15 02:48:41 +0000
commit6e9846255dbb5b9bcf572536b757e998d6471117 (patch)
treef3f307677c073de5be1c9e45fd11f6813d972abf /components/proximity_auth
parent7458fb3730f7cfc084f310a7cbebde489aa78439 (diff)
downloadchromium_src-6e9846255dbb5b9bcf572536b757e998d6471117.zip
chromium_src-6e9846255dbb5b9bcf572536b757e998d6471117.tar.gz
chromium_src-6e9846255dbb5b9bcf572536b757e998d6471117.tar.bz2
[Proximity Auth] Port the UnlockManager class.
This ports most of the code, with a few TODOs remaining for future CLs. BUG=501626 R=tengs@chromium.org Review URL: https://codereview.chromium.org/1239193005 Cr-Commit-Position: refs/heads/master@{#343537}
Diffstat (limited to 'components/proximity_auth')
-rw-r--r--components/proximity_auth/BUILD.gn4
-rw-r--r--components/proximity_auth/DEPS2
-rw-r--r--components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc5
-rw-r--r--components/proximity_auth/controller.h34
-rw-r--r--components/proximity_auth/metrics.cc8
-rw-r--r--components/proximity_auth/metrics.h17
-rw-r--r--components/proximity_auth/proximity_auth_client.h11
-rw-r--r--components/proximity_auth/proximity_monitor_impl_unittest.cc1
-rw-r--r--components/proximity_auth/unlock_manager.cc467
-rw-r--r--components/proximity_auth/unlock_manager.h197
-rw-r--r--components/proximity_auth/unlock_manager_unittest.cc814
11 files changed, 1557 insertions, 3 deletions
diff --git a/components/proximity_auth/BUILD.gn b/components/proximity_auth/BUILD.gn
index 4541e8c..5c45881 100644
--- a/components/proximity_auth/BUILD.gn
+++ b/components/proximity_auth/BUILD.gn
@@ -25,6 +25,7 @@ source_set("proximity_auth") {
"connection.h",
"connection_finder.h",
"connection_observer.h",
+ "controller.h",
"device_to_device_authenticator.cc",
"device_to_device_authenticator.h",
"device_to_device_initiator_operations.cc",
@@ -52,6 +53,8 @@ source_set("proximity_auth") {
"switches.h",
"throttled_bluetooth_connection_finder.cc",
"throttled_bluetooth_connection_finder.h",
+ "unlock_manager.cc",
+ "unlock_manager.h",
"wire_message.cc",
"wire_message.h",
]
@@ -94,6 +97,7 @@ source_set("unit_tests") {
"proximity_monitor_impl_unittest.cc",
"remote_status_update_unittest.cc",
"throttled_bluetooth_connection_finder_unittest.cc",
+ "unlock_manager_unittest.cc",
"wire_message_unittest.cc",
]
diff --git a/components/proximity_auth/DEPS b/components/proximity_auth/DEPS
index 161da31..24229c6 100644
--- a/components/proximity_auth/DEPS
+++ b/components/proximity_auth/DEPS
@@ -4,7 +4,7 @@ include_rules = [
]
specific_include_rules = {
- "screenlock_bridge\.*": [
+ "screenlock_bridge\.*|unlock_manager\.*": [
"+chromeos",
],
}
diff --git a/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc b/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc
index e178a0b..79fd6ef 100644
--- a/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc
+++ b/components/proximity_auth/ble/proximity_auth_ble_system_unittest.cc
@@ -71,7 +71,10 @@ class MockProximityAuthClient : public ProximityAuthClient {
// ProximityAuthClient:
std::string GetAuthenticatedUsername() const override { return kTestUser; }
- MOCK_METHOD1(FinalizeUnlock, void(bool));
+ MOCK_METHOD1(UpdateScreenlockState,
+ void(proximity_auth::ScreenlockState state));
+ MOCK_METHOD1(FinalizeUnlock, void(bool success));
+ MOCK_METHOD1(FinalizeSignin, void(const std::string& secret));
private:
DISALLOW_COPY_AND_ASSIGN(MockProximityAuthClient);
diff --git a/components/proximity_auth/controller.h b/components/proximity_auth/controller.h
new file mode 100644
index 0000000..4261e78
--- /dev/null
+++ b/components/proximity_auth/controller.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef COMPONENTS_PROXIMITY_AUTH_CONTROLLER_H
+#define COMPONENTS_PROXIMITY_AUTH_CONTROLLER_H
+
+#include "base/macros.h"
+
+namespace proximity_auth {
+
+class Client;
+
+// Main class that controls the logic flow of the Easy Unlock system.
+class Controller {
+ public:
+ enum class State {
+ NOT_STARTED,
+ FINDING_CONNECTION,
+ AUTHENTICATING,
+ SECURE_CHANNEL_ESTABLISHED,
+ AUTHENTICATION_FAILED,
+ STOPPED,
+ };
+
+ virtual ~Controller() {}
+
+ virtual State GetState() const = 0;
+ virtual Client* GetClient() = 0;
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_CONTROLLER_H
diff --git a/components/proximity_auth/metrics.cc b/components/proximity_auth/metrics.cc
index 99f74b1..7b4db8f 100644
--- a/components/proximity_auth/metrics.cc
+++ b/components/proximity_auth/metrics.cc
@@ -4,6 +4,7 @@
#include "components/proximity_auth/metrics.h"
+#include "base/logging.h"
#include "base/md5.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
@@ -69,5 +70,12 @@ void RecordAuthProximityRemoteDeviceModelHash(const std::string& device_model) {
HashDeviceModelName(device_model));
}
+void RecordRemoteSecuritySettingsState(RemoteSecuritySettingsState state) {
+ DCHECK(state < RemoteSecuritySettingsState::COUNT);
+ UMA_HISTOGRAM_ENUMERATION(
+ "EasyUnlock.RemoteLockScreenState", static_cast<int>(state),
+ static_cast<int>(RemoteSecuritySettingsState::COUNT));
+}
+
} // namespace metrics
} // namespace proximity_auth
diff --git a/components/proximity_auth/metrics.h b/components/proximity_auth/metrics.h
index 5a0e20a..a1ac3cd 100644
--- a/components/proximity_auth/metrics.h
+++ b/components/proximity_auth/metrics.h
@@ -13,6 +13,19 @@ namespace metrics {
extern const char kUnknownDeviceModel[];
extern const int kUnknownProximityValue;
+// Possible states of the remote device's security settings. This enum is used
+// to back a histogram, and hence should be treated as append-only.
+enum class RemoteSecuritySettingsState {
+ UNKNOWN,
+ SCREEN_LOCK_DISABLED_TRUST_AGENT_UNSUPPORTED,
+ SCREEN_LOCK_DISABLED_TRUST_AGENT_DISABLED,
+ SCREEN_LOCK_DISABLED_TRUST_AGENT_ENABLED,
+ SCREEN_LOCK_ENABLED_TRUST_AGENT_UNSUPPORTED,
+ SCREEN_LOCK_ENABLED_TRUST_AGENT_DISABLED,
+ SCREEN_LOCK_ENABLED_TRUST_AGENT_ENABLED,
+ COUNT
+};
+
// Records the current |rolling_rssi| reading, upon a successful auth attempt.
// |rolling_rssi| should be set to |kUnknownProximityValue| if no RSSI readings
// are available.
@@ -33,6 +46,10 @@ void RecordAuthProximityTimeSinceLastZeroRssi(
// set to |kUnknownDeviceModel| if the device model could not be read.
void RecordAuthProximityRemoteDeviceModelHash(const std::string& device_model);
+// Records the screen lock and trust agent settings state of the remote device,
+// as received in a status update from the remote device.
+void RecordRemoteSecuritySettingsState(RemoteSecuritySettingsState state);
+
} // namespace metrics
} // namespace proximity_auth
diff --git a/components/proximity_auth/proximity_auth_client.h b/components/proximity_auth/proximity_auth_client.h
index eb846cd..b5d2c7b 100644
--- a/components/proximity_auth/proximity_auth_client.h
+++ b/components/proximity_auth/proximity_auth_client.h
@@ -7,6 +7,8 @@
#include <string>
+#include "components/proximity_auth/screenlock_state.h"
+
namespace proximity_auth {
// An interface that needs to be supplied to the Proximity Auth component by its
@@ -19,10 +21,19 @@ class ProximityAuthClient {
// Returns the authenticated username.
virtual std::string GetAuthenticatedUsername() const = 0;
+ // Updates the user pod on the signin or lock screen to reflect the provided
+ // screenlock state.
+ virtual void UpdateScreenlockState(ScreenlockState state) = 0;
+
// Finalizes an unlock attempt initiated by the user. If |success| is true,
// the screen is unlocked; otherwise, the auth attempt is rejected. An auth
// attempt must be in progress before calling this function.
virtual void FinalizeUnlock(bool success) = 0;
+
+ // Finalizes a sign-in attempt initiated by the user. If |success| is true,
+ // the user is signed in; otherwise, the auth attempt is rejected. An auth
+ // attempt must be in progress before calling this function.
+ virtual void FinalizeSignin(const std::string& secret) = 0;
};
} // namespace proximity_auth
diff --git a/components/proximity_auth/proximity_monitor_impl_unittest.cc b/components/proximity_auth/proximity_monitor_impl_unittest.cc
index 4231934..12f74ed 100644
--- a/components/proximity_auth/proximity_monitor_impl_unittest.cc
+++ b/components/proximity_auth/proximity_monitor_impl_unittest.cc
@@ -7,7 +7,6 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
-#include "base/run_loop.h"
#include "base/test/histogram_tester.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/test_simple_task_runner.h"
diff --git a/components/proximity_auth/unlock_manager.cc b/components/proximity_auth/unlock_manager.cc
new file mode 100644
index 0000000..d9156fc
--- /dev/null
+++ b/components/proximity_auth/unlock_manager.cc
@@ -0,0 +1,467 @@
+// 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"
+
+using chromeos::DBusThreadManager;
+#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);
+ OnScreenLockedOrUnlocked(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_);
+
+ 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) {
+ OnScreenLockedOrUnlocked(true);
+}
+
+void UnlockManager::OnScreenDidUnlock(
+ ScreenlockBridge::LockHandler::ScreenType screen_type) {
+ OnScreenLockedOrUnlocked(false);
+}
+
+void UnlockManager::OnFocusedUserChanged(const std::string& user_id) {}
+
+void UnlockManager::OnScreenLockedOrUnlocked(bool is_locked) {
+ // 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
diff --git a/components/proximity_auth/unlock_manager.h b/components/proximity_auth/unlock_manager.h
new file mode 100644
index 0000000..9f40541
--- /dev/null
+++ b/components/proximity_auth/unlock_manager.h
@@ -0,0 +1,197 @@
+// 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.
+
+#ifndef COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_H
+#define COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_H
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "components/proximity_auth/client_observer.h"
+#include "components/proximity_auth/controller.h"
+#include "components/proximity_auth/remote_status_update.h"
+#include "components/proximity_auth/screenlock_bridge.h"
+#include "components/proximity_auth/screenlock_state.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/power_manager_client.h"
+#endif
+
+namespace proximity_auth {
+
+class Client;
+class ProximityAuthClient;
+class ProximityMonitor;
+
+// The unlock manager is responsible for controlling the lock screen UI based on
+// the authentication status of the registered remote devices.
+class UnlockManager : public ClientObserver,
+ public ScreenlockBridge::Observer,
+#if defined(OS_CHROMEOS)
+ chromeos::PowerManagerClient::Observer,
+#endif // defined(OS_CHROMEOS)
+ public device::BluetoothAdapter::Observer {
+ public:
+ enum class ScreenlockType {
+ SESSION_LOCK,
+ SIGN_IN,
+ };
+
+ // The |proximity_auth_client| is not owned and should outlive the constructed
+ // unlock manager.
+ // TODO(isherman): Rather than passing a single ProximityMonitor instance, we
+ // should pass a factory, as the UnlockManager should create and destroy
+ // ProximityMonitors as needed. Currently, the expectations are misaligned
+ // between the ProximityMonitor and the UnlockManager classes.
+ UnlockManager(ScreenlockType screenlock_type,
+ scoped_ptr<ProximityMonitor> proximity_monitor,
+ ProximityAuthClient* proximity_auth_client);
+ ~UnlockManager() override;
+
+ // Whether proximity-based unlocking is currently allowed. True if any one of
+ // the remote devices is authenticated and in range.
+ bool IsUnlockAllowed();
+
+ // Sets the |controller| to which local events are dispatched. A null
+ // controller indicates that proximity-based authentication is inactive.
+ void SetController(Controller* controller);
+
+ // Called when the controller's state changes.
+ void OnControllerStateChanged();
+
+ protected:
+ // Called when the user pod is clicked for an authentication attempt of type
+ // |auth_type|.
+ // Exposed for testing.
+ void OnAuthAttempted(ScreenlockBridge::LockHandler::AuthType auth_type);
+
+ private:
+ // The possible lock screen states for the remote device.
+ enum class RemoteScreenlockState {
+ UNKNOWN,
+ UNLOCKED,
+ DISABLED,
+ LOCKED,
+ };
+
+ // ClientObserver:
+ void OnUnlockEventSent(bool success) override;
+ void OnRemoteStatusUpdate(const RemoteStatusUpdate& status_update) override;
+ void OnDecryptResponse(scoped_ptr<std::string> decrypted_bytes) override;
+ void OnUnlockResponse(bool success) override;
+ void OnDisconnected() override;
+
+ // ScreenlockBridge::Observer
+ void OnScreenDidLock(
+ ScreenlockBridge::LockHandler::ScreenType screen_type) override;
+ void OnScreenDidUnlock(
+ ScreenlockBridge::LockHandler::ScreenType screen_type) override;
+ void OnFocusedUserChanged(const std::string& user_id) override;
+
+ // Called when the screenlock state changes.
+ void OnScreenLockedOrUnlocked(bool is_locked);
+
+ // Called when the Bluetooth adapter is initialized.
+ void OnBluetoothAdapterInitialized(
+ scoped_refptr<device::BluetoothAdapter> adapter);
+
+ // device::BluetoothAdapter::Observer:
+ void AdapterPresentChanged(device::BluetoothAdapter* adapter,
+ bool present) override;
+ void AdapterPoweredChanged(device::BluetoothAdapter* adapter,
+ bool powered) override;
+
+#if defined(OS_CHROMEOS)
+ // chromeos::PowerManagerClient::Observer:
+ void SuspendDone(const base::TimeDelta& sleep_duration) override;
+#endif // defined(OS_CHROMEOS)
+
+ // Called when auth is attempted to send the sign-in challenge to the remote
+ // device for decryption.
+ void SendSignInChallenge();
+
+ // Returns the current state for the screen lock UI.
+ ScreenlockState GetScreenlockState();
+
+ // Updates the lock screen based on the manager's current state.
+ void UpdateLockScreen();
+
+ // Activates or deactivates the proximity monitor, as appropriate given the
+ // current state of |this| unlock manager.
+ void UpdateProximityMonitorState();
+
+ // Sets waking up state.
+ void SetWakingUpState(bool is_waking_up);
+
+ // Accepts or rejects the current auth attempt according to |should_accept|.
+ // If the auth attempt is accepted, unlocks the screen.
+ void AcceptAuthAttempt(bool should_accept);
+
+ // Returns the screen lock state corresponding to the given remote |status|
+ // update.
+ RemoteScreenlockState GetScreenlockStateFromRemoteUpdate(
+ RemoteStatusUpdate update);
+
+ // Whether |this| manager is being used for sign-in or session unlock.
+ const ScreenlockType screenlock_type_;
+
+ // Whether the user is present at the remote device. Unset if no remote status
+ // update has yet been received.
+ scoped_ptr<RemoteScreenlockState> remote_screenlock_state_;
+
+ // Controls the proximity auth flow logic. Not owned, and expcted to outlive
+ // |this| instance.
+ Controller* controller_;
+
+ // The client used to communicate with the remote device once a secure channel
+ // is established. Null if no secure channel has been established yet. Not
+ // owned, and expected to outlive |this| instance.
+ Client* client_;
+
+ // Tracks whether the remote device is currently in close enough proximity to
+ // the local device to allow unlocking.
+ scoped_ptr<ProximityMonitor> proximity_monitor_;
+
+ // Used to call into the embedder. Expected to outlive |this| instance.
+ ProximityAuthClient* proximity_auth_client_;
+
+ // Whether the screen is currently locked.
+ bool is_locked_;
+
+ // True if the manager is currently processing a user-initiated authentication
+ // attempt, which is initiated when the user pod is clicked.
+ bool is_attempting_auth_;
+
+ // Whether the system is waking up from sleep.
+ bool is_waking_up_;
+
+ // The Bluetooth adapter. Null if there is no adapter present on the local
+ // device.
+ scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
+
+ // The sign-in secret received from the remote device by decrypting the
+ // sign-in challenge.
+ scoped_ptr<std::string> sign_in_secret_;
+
+ // The state of the current screen lock UI.
+ ScreenlockState screenlock_state_;
+
+ // Used to clear the waking up state after a timeout.
+ base::WeakPtrFactory<UnlockManager> clear_waking_up_state_weak_ptr_factory_;
+
+ // Used to reject auth attempts after a timeout. An in-progress auth attempt
+ // blocks the sign-in screen UI, so it's important to prevent the auth attempt
+ // from blocking the UI in case a step in the code path hangs.
+ base::WeakPtrFactory<UnlockManager> reject_auth_attempt_weak_ptr_factory_;
+
+ // Used to vend all other weak pointers.
+ base::WeakPtrFactory<UnlockManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnlockManager);
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_H
diff --git a/components/proximity_auth/unlock_manager_unittest.cc b/components/proximity_auth/unlock_manager_unittest.cc
new file mode 100644
index 0000000..effc52f
--- /dev/null
+++ b/components/proximity_auth/unlock_manager_unittest.cc
@@ -0,0 +1,814 @@
+// 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/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/proximity_auth/client.h"
+#include "components/proximity_auth/controller.h"
+#include "components/proximity_auth/logging/logging.h"
+#include "components/proximity_auth/proximity_auth_client.h"
+#include "components/proximity_auth/proximity_monitor.h"
+#include "components/proximity_auth/remote_status_update.h"
+#include "components/proximity_auth/screenlock_bridge.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/dbus_thread_manager.h"
+#endif // defined(OS_CHROMEOS)
+
+using testing::AtLeast;
+using testing::NiceMock;
+using testing::Return;
+using testing::_;
+
+namespace proximity_auth {
+namespace {
+
+// Note that the trust agent state is currently ignored by the UnlockManager
+// implementation.
+RemoteStatusUpdate kRemoteScreenUnlocked = {
+ USER_PRESENT, SECURE_SCREEN_LOCK_ENABLED, TRUST_AGENT_UNSUPPORTED};
+RemoteStatusUpdate kRemoteScreenLocked = {
+ USER_ABSENT, SECURE_SCREEN_LOCK_ENABLED, TRUST_AGENT_UNSUPPORTED};
+RemoteStatusUpdate kRemoteScreenlockDisabled = {
+ USER_PRESENT, SECURE_SCREEN_LOCK_DISABLED, TRUST_AGENT_UNSUPPORTED};
+RemoteStatusUpdate kRemoteScreenlockStateUnknown = {
+ USER_PRESENCE_UNKNOWN, SECURE_SCREEN_LOCK_STATE_UNKNOWN,
+ TRUST_AGENT_UNSUPPORTED};
+
+class MockController : public Controller {
+ public:
+ MockController() {}
+ ~MockController() override {}
+
+ MOCK_CONST_METHOD0(GetState, State());
+ MOCK_METHOD0(GetClient, Client*());
+};
+
+class MockClient : public Client {
+ public:
+ MockClient() {}
+ ~MockClient() override {}
+
+ MOCK_METHOD1(AddObserver, void(ClientObserver* observer));
+ MOCK_METHOD1(RemoveObserver, void(ClientObserver* observer));
+ MOCK_CONST_METHOD0(SupportsSignIn, bool());
+ MOCK_METHOD0(DispatchUnlockEvent, void());
+ MOCK_METHOD1(RequestDecryption, void(const std::string& challenge));
+ MOCK_METHOD0(RequestUnlock, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockClient);
+};
+
+class MockProximityMonitor : public ProximityMonitor {
+ public:
+ MockProximityMonitor() {
+ ON_CALL(*this, GetStrategy())
+ .WillByDefault(Return(ProximityMonitor::Strategy::NONE));
+ ON_CALL(*this, IsUnlockAllowed()).WillByDefault(Return(true));
+ ON_CALL(*this, IsInRssiRange()).WillByDefault(Return(false));
+ }
+ ~MockProximityMonitor() override {}
+
+ MOCK_METHOD0(Start, void());
+ MOCK_METHOD0(Stop, void());
+ MOCK_CONST_METHOD0(GetStrategy, Strategy());
+ MOCK_CONST_METHOD0(IsUnlockAllowed, bool());
+ MOCK_CONST_METHOD0(IsInRssiRange, bool());
+ MOCK_METHOD0(RecordProximityMetricsOnAuthSuccess, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockProximityMonitor);
+};
+
+class MockProximityAuthClient : public ProximityAuthClient {
+ public:
+ MockProximityAuthClient() {}
+ ~MockProximityAuthClient() override {}
+
+ MOCK_CONST_METHOD0(GetAuthenticatedUsername, std::string());
+ MOCK_METHOD1(UpdateScreenlockState,
+ void(proximity_auth::ScreenlockState state));
+ MOCK_METHOD1(FinalizeUnlock, void(bool success));
+ MOCK_METHOD1(FinalizeSignin, void(const std::string& secret));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockProximityAuthClient);
+};
+
+class FakeLockHandler : public ScreenlockBridge::LockHandler {
+ public:
+ FakeLockHandler() {}
+ ~FakeLockHandler() override {}
+
+ // LockHandler:
+ void ShowBannerMessage(const base::string16& message) override {}
+ void ShowUserPodCustomIcon(
+ const std::string& user_email,
+ const ScreenlockBridge::UserPodCustomIconOptions& icon) override {}
+ void HideUserPodCustomIcon(const std::string& user_email) override {}
+ void EnableInput() override {}
+ void SetAuthType(const std::string& user_email,
+ AuthType auth_type,
+ const base::string16& auth_value) override {}
+ AuthType GetAuthType(const std::string& user_email) const override {
+ return USER_CLICK;
+ }
+ ScreenType GetScreenType() const override { return LOCK_SCREEN; }
+ void Unlock(const std::string& user_email) override {}
+ void AttemptEasySignin(const std::string& user_email,
+ const std::string& secret,
+ const std::string& key_label) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeLockHandler);
+};
+
+class TestUnlockManager : public UnlockManager {
+ public:
+ TestUnlockManager(ScreenlockType screenlock_type,
+ scoped_ptr<ProximityMonitor> proximity_monitor,
+ ProximityAuthClient* proximity_auth_client)
+ : UnlockManager(screenlock_type,
+ proximity_monitor.Pass(),
+ proximity_auth_client) {}
+ ~TestUnlockManager() override {}
+
+ using UnlockManager::OnAuthAttempted;
+ using ClientObserver::OnUnlockEventSent;
+ using ClientObserver::OnRemoteStatusUpdate;
+ using ClientObserver::OnDecryptResponse;
+ using ClientObserver::OnUnlockResponse;
+ using ClientObserver::OnDisconnected;
+ using ScreenlockBridge::Observer::OnScreenDidLock;
+ using ScreenlockBridge::Observer::OnScreenDidUnlock;
+ using ScreenlockBridge::Observer::OnFocusedUserChanged;
+};
+
+// Creates a mock Bluetooth adapter and sets it as the global adapter for
+// testing.
+scoped_refptr<device::MockBluetoothAdapter>
+CreateAndRegisterMockBluetoothAdapter() {
+ scoped_refptr<device::MockBluetoothAdapter> adapter =
+ new NiceMock<device::MockBluetoothAdapter>();
+ device::BluetoothAdapterFactory::SetAdapterForTesting(adapter);
+ return adapter;
+}
+
+} // namespace
+
+class ProximityAuthUnlockManagerTest : public testing::Test {
+ public:
+ ProximityAuthUnlockManagerTest()
+ : bluetooth_adapter_(CreateAndRegisterMockBluetoothAdapter()),
+ proximity_monitor_(nullptr),
+ task_runner_(new base::TestSimpleTaskRunner()),
+ thread_task_runner_handle_(task_runner_) {
+ ON_CALL(*bluetooth_adapter_, IsPowered()).WillByDefault(Return(true));
+ ON_CALL(controller_, GetClient()).WillByDefault(Return(&client_));
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true));
+
+ ScreenlockBridge::Get()->SetLockHandler(&lock_handler_);
+
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Initialize();
+#endif
+ }
+
+ ~ProximityAuthUnlockManagerTest() override {
+ // Make sure to verify the mock prior to the destruction of the unlock
+ // manager, as otherwise it's impossible to tell whether calls to Stop()
+ // occur as a side-effect of the destruction or from the code intended to be
+ // under test.
+ if (proximity_monitor_)
+ testing::Mock::VerifyAndClearExpectations(proximity_monitor_);
+
+ // The UnlockManager must be destroyed before calling
+ // chromeos::DBusThreadManager::Shutdown(), as the UnlockManager's
+ // destructor references the DBusThreadManager.
+ unlock_manager_.reset();
+
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Shutdown();
+#endif
+
+ ScreenlockBridge::Get()->SetLockHandler(nullptr);
+ }
+
+ void CreateUnlockManager(UnlockManager::ScreenlockType screenlock_type) {
+ proximity_monitor_ = new NiceMock<MockProximityMonitor>;
+ unlock_manager_.reset(new TestUnlockManager(
+ screenlock_type, make_scoped_ptr(proximity_monitor_),
+ &proximity_auth_client_));
+ }
+
+ void SimulateUserPresentState() {
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+ unlock_manager_->SetController(&controller_);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->OnControllerStateChanged();
+
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+ }
+
+ void RunPendingTasks() { task_runner_->RunPendingTasks(); }
+
+ protected:
+ // Mock used for verifying interactions with the Bluetooth subsystem.
+ scoped_refptr<device::MockBluetoothAdapter> bluetooth_adapter_;
+
+ NiceMock<MockProximityAuthClient> proximity_auth_client_;
+ NiceMock<MockController> controller_;
+ NiceMock<MockClient> client_;
+ scoped_ptr<TestUnlockManager> unlock_manager_;
+ // Owned by the |unlock_manager_|.
+ MockProximityMonitor* proximity_monitor_;
+
+ private:
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle thread_task_runner_handle_;
+ FakeLockHandler lock_handler_;
+ ScopedDisableLoggingForTesting disable_logging_;
+};
+
+TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_InitialState) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_SessionLock_AllGood) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->SetController(&controller_);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+
+ EXPECT_TRUE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_SignIn_AllGood) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SIGN_IN);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+ unlock_manager_->SetController(&controller_);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->OnControllerStateChanged();
+
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true));
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+
+ EXPECT_TRUE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ IsUnlockAllowed_SignIn_ClientDoesNotSupportSignIn) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SIGN_IN);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+ unlock_manager_->SetController(&controller_);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->OnControllerStateChanged();
+
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(false));
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_SignIn_ClientIsNull) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SIGN_IN);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ ON_CALL(controller_, GetClient()).WillByDefault(Return(nullptr));
+ unlock_manager_->SetController(&controller_);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ IsUnlockAllowed_DisallowedByProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->SetController(&controller_);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+
+ ON_CALL(*proximity_monitor_, IsUnlockAllowed()).WillByDefault(Return(false));
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ IsUnlockAllowed_SecureChannelNotEstablished) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::AUTHENTICATING));
+ unlock_manager_->SetController(&controller_);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, IsUnlockAllowed_ControllerIsNull) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ unlock_manager_->SetController(nullptr);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenUnlocked);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ IsUnlockAllowed_RemoteScreenlockStateLocked) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->SetController(&controller_);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenLocked);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ IsUnlockAllowed_RemoteScreenlockStateUnknown) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->SetController(&controller_);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenlockStateUnknown);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ IsUnlockAllowed_RemoteScreenlockStateDisabled) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->SetController(&controller_);
+ unlock_manager_->OnRemoteStatusUpdate(kRemoteScreenlockDisabled);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ IsUnlockAllowed_RemoteScreenlockStateNotYetReceived) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->SetController(&controller_);
+
+ EXPECT_FALSE(unlock_manager_->IsUnlockAllowed());
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, SetController_SetToNull) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::INACTIVE));
+ unlock_manager_->SetController(nullptr);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, SetController_ExistingController) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(proximity_auth_client_, UpdateScreenlockState(_)).Times(0);
+ unlock_manager_->SetController(&controller_);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ SetController_NullThenExistingController) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::INACTIVE));
+ unlock_manager_->SetController(nullptr);
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::AUTHENTICATED));
+ unlock_manager_->SetController(&controller_);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, SetController_AuthenticationFailed) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ unlock_manager_->SetController(nullptr);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED));
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::PHONE_NOT_AUTHENTICATED));
+ unlock_manager_->SetController(&controller_);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, SetController_WakingUp) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ unlock_manager_->SetController(nullptr);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::FINDING_CONNECTION));
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+ unlock_manager_->SetController(&controller_);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ SetController_NullController_StopsProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(*proximity_monitor_, Stop()).Times(AtLeast(1));
+ unlock_manager_->SetController(nullptr);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ SetController_ConnectingController_StopsProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ NiceMock<MockController> controller;
+ ON_CALL(controller, GetState())
+ .WillByDefault(Return(Controller::State::FINDING_CONNECTION));
+
+ EXPECT_CALL(*proximity_monitor_, Stop()).Times(AtLeast(1));
+ unlock_manager_->SetController(&controller);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ SetController_ConnectedController_StartsProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ NiceMock<MockController> controller;
+ ON_CALL(controller, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+
+ EXPECT_CALL(*proximity_monitor_, Start()).Times(AtLeast(1));
+ unlock_manager_->SetController(&controller);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_SecureChannelEstablished_RegistersAsObserver) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ NiceMock<MockController> controller;
+ ON_CALL(controller, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+
+ EXPECT_CALL(client_, AddObserver(unlock_manager_.get()));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_StartsProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ NiceMock<MockController> controller;
+ ON_CALL(controller, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+
+ EXPECT_CALL(*proximity_monitor_, Start()).Times(AtLeast(1));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_StopsProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED));
+
+ EXPECT_CALL(*proximity_monitor_, Stop()).Times(AtLeast(1));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_Stopped_UpdatesScreenlockState) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::INACTIVE));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_AuthenticationFailed_UpdatesScreenlockState) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED));
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::PHONE_NOT_AUTHENTICATED));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_FindingConnection_UpdatesScreenlockState) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+ unlock_manager_->SetController(&controller_);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::FINDING_CONNECTION));
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_Authenticating_UpdatesScreenlockState) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+ unlock_manager_->SetController(&controller_);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::AUTHENTICATING));
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(
+ ProximityAuthUnlockManagerTest,
+ OnControllerStateChanged_SecureChannelEstablished_UpdatesScreenlockState) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+ unlock_manager_->SetController(&controller_);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+ unlock_manager_->OnControllerStateChanged();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnDisconnected_UnregistersAsObserver) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::AUTHENTICATION_FAILED));
+
+ EXPECT_CALL(client_, RemoveObserver(unlock_manager_.get()));
+ unlock_manager_.get()->OnDisconnected();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnScreenDidUnlock_StopsProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(*proximity_monitor_, Stop());
+ unlock_manager_.get()->OnScreenDidUnlock(
+ ScreenlockBridge::LockHandler::LOCK_SCREEN);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnScreenDidLock_StartsProximityMonitor) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::STOPPED));
+ unlock_manager_->SetController(&controller_);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::SECURE_CHANNEL_ESTABLISHED));
+ unlock_manager_->OnControllerStateChanged();
+
+ EXPECT_CALL(*proximity_monitor_, Start());
+ unlock_manager_.get()->OnScreenDidLock(
+ ScreenlockBridge::LockHandler::LOCK_SCREEN);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnScreenDidLock_SetsWakingUpState) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ unlock_manager_.get()->OnScreenDidUnlock(
+ ScreenlockBridge::LockHandler::LOCK_SCREEN);
+
+ ON_CALL(controller_, GetState())
+ .WillByDefault(Return(Controller::State::FINDING_CONNECTION));
+ unlock_manager_->OnControllerStateChanged();
+
+ EXPECT_CALL(proximity_auth_client_,
+ UpdateScreenlockState(ScreenlockState::BLUETOOTH_CONNECTING));
+ unlock_manager_.get()->OnScreenDidLock(
+ ScreenlockBridge::LockHandler::LOCK_SCREEN);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnDecryptResponse_NoAuthAttemptInProgress) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0);
+ unlock_manager_.get()->OnDecryptResponse(nullptr);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnUnlockEventSent_NoAuthAttemptInProgress) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0);
+ unlock_manager_.get()->OnUnlockEventSent(true);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnUnlockResponse_NoAuthAttemptInProgress) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0);
+ unlock_manager_.get()->OnUnlockResponse(true);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_NoController) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ unlock_manager_->SetController(nullptr);
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false));
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_UnlockNotAllowed) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ ON_CALL(*proximity_monitor_, IsUnlockAllowed()).WillByDefault(Return(false));
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false));
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_NotUserClick) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0);
+ unlock_manager_->OnAuthAttempted(
+ ScreenlockBridge::LockHandler::EXPAND_THEN_USER_CLICK);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_DuplicateCall) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(client_, RequestUnlock());
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ EXPECT_CALL(client_, RequestUnlock()).Times(0);
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest, OnAuthAttempted_TimesOut) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ // Simulate the timeout period elapsing.
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false));
+ RunPendingTasks();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnAuthAttempted_DoesntTimeOutFollowingResponse) {
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_));
+ unlock_manager_->OnUnlockResponse(false);
+
+ // Simulate the timeout period elapsing.
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(_)).Times(0);
+ RunPendingTasks();
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnAuthAttempted_Unlock_SupportsSignIn_UnlockRequestFails) {
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true));
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(client_, RequestUnlock());
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false));
+ unlock_manager_->OnUnlockResponse(false);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnAuthAttempted_Unlock_WithSignIn_RequestSucceeds_EventSendFails) {
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true));
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(client_, RequestUnlock());
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ EXPECT_CALL(client_, DispatchUnlockEvent());
+ unlock_manager_->OnUnlockResponse(true);
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false));
+ unlock_manager_->OnUnlockEventSent(false);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnAuthAttempted_Unlock_WithSignIn_RequestSucceeds_EventSendSucceeds) {
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(true));
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(client_, RequestUnlock());
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ EXPECT_CALL(client_, DispatchUnlockEvent());
+ unlock_manager_->OnUnlockResponse(true);
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(true));
+ unlock_manager_->OnUnlockEventSent(true);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnAuthAttempted_Unlock_DoesntSupportSignIn_UnlockEventSendFails) {
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(false));
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(client_, DispatchUnlockEvent());
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(false));
+ unlock_manager_->OnUnlockEventSent(false);
+}
+
+TEST_F(ProximityAuthUnlockManagerTest,
+ OnAuthAttempted_Unlock_SupportsSignIn_UnlockEventSendSucceeds) {
+ ON_CALL(client_, SupportsSignIn()).WillByDefault(Return(false));
+ CreateUnlockManager(UnlockManager::ScreenlockType::SESSION_LOCK);
+ SimulateUserPresentState();
+
+ EXPECT_CALL(client_, DispatchUnlockEvent());
+ unlock_manager_->OnAuthAttempted(ScreenlockBridge::LockHandler::USER_CLICK);
+
+ EXPECT_CALL(proximity_auth_client_, FinalizeUnlock(true));
+ unlock_manager_->OnUnlockEventSent(true);
+}
+
+} // namespace proximity_auth