chromium/chromeos/ash/components/proximity_auth/unlock_manager_impl.h

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_ASH_COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_IMPL_H_
#define CHROMEOS_ASH_COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_IMPL_H_

#include "ash/public/cpp/smartlock_state.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chromeos/ash/components/proximity_auth/messenger_observer.h"
#include "chromeos/ash/components/proximity_auth/proximity_auth_system.h"
#include "chromeos/ash/components/proximity_auth/proximity_monitor_observer.h"
#include "chromeos/ash/components/proximity_auth/remote_device_life_cycle.h"
#include "chromeos/ash/components/proximity_auth/remote_status_update.h"
#include "chromeos/ash/components/proximity_auth/screenlock_bridge.h"
#include "chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h"
#include "chromeos/ash/components/proximity_auth/unlock_manager.h"
#include "chromeos/ash/services/secure_channel/public/mojom/secure_channel.mojom.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "device/bluetooth/bluetooth_adapter.h"

namespace base {
class OneShotTimer;
}  // namespace base

namespace proximity_auth {

class Messenger;
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 UnlockManagerImpl : public UnlockManager,
                          public MessengerObserver,
                          public ProximityMonitorObserver,
                          public chromeos::PowerManagerClient::Observer,
                          public device::BluetoothAdapter::Observer,
                          public RemoteDeviceLifeCycle::Observer {
 public:
  // The |proximity_auth_client| is not owned and should outlive the constructed
  // unlock manager.
  explicit UnlockManagerImpl(ProximityAuthClient* proximity_auth_client);

  UnlockManagerImpl(const UnlockManagerImpl&) = delete;
  UnlockManagerImpl& operator=(const UnlockManagerImpl&) = delete;

  ~UnlockManagerImpl() override;

  // UnlockManager:
  bool IsUnlockAllowed() override;
  void SetRemoteDeviceLifeCycle(RemoteDeviceLifeCycle* life_cycle) override;
  void OnAuthAttempted(mojom::AuthType auth_type) override;
  void CancelConnectionAttempt() override;
  std::string GetLastRemoteStatusUnlockForLogging() override;

 protected:
  // Creates a ProximityMonitor instance for the given |connection|.
  // Exposed for testing.
  virtual std::unique_ptr<ProximityMonitor> CreateProximityMonitor(
      RemoteDeviceLifeCycle* life_cycle);

 private:
  friend class ProximityAuthUnlockManagerImplTest;

  // The possible lock screen states for the remote device.
  enum class RemoteScreenlockState {
    UNKNOWN,
    UNLOCKED,
    DISABLED,
    LOCKED,
    PRIMARY_USER_ABSENT,
  };

  // This enum is tied directly to a UMA enum defined in
  // //tools/metrics/histograms/enums.xml, and should always reflect it (do not
  // change one without changing the other). Entries should be never modified
  // or deleted. Only additions possible.
  enum class GetRemoteStatusResultFailureReason {
    kCanceledBluetoothDisabled = 0,
    kDeprecatedTimedOutCouldNotEstablishAuthenticatedChannel = 1,
    kTimedOutDidNotReceiveRemoteStatusUpdate = 2,
    kDeprecatedUserEnteredPasswordWhileBluetoothDisabled = 3,
    kCanceledUserEnteredPassword = 4,
    kAuthenticatedChannelDropped = 5,
    kMaxValue = kAuthenticatedChannelDropped
  };

  // MessengerObserver:
  void OnUnlockEventSent(bool success) override;
  void OnRemoteStatusUpdate(const RemoteStatusUpdate& status_update) override;
  void OnUnlockResponse(bool success) override;
  void OnDisconnected() override;

  // ProximityMonitorObserver:
  void OnProximityStateChanged() 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;

  // chromeos::PowerManagerClient::Observer:
  void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
  void SuspendDone(base::TimeDelta sleep_duration) override;

  // RemoteDeviceLifeCycle::Observer:
  void OnLifeCycleStateChanged(RemoteDeviceLifeCycle::State old_state,
                               RemoteDeviceLifeCycle::State new_state) override;

  // Returns true if the BluetoothAdapter is present and powered.
  bool IsBluetoothPresentAndPowered() const;

  // TODO(crbug.com/986896): Waiting a certain time, after resume, before
  // trusting the presence and power values returned by BluetoothAdapter is
  // necessary because the BluetoothAdapter returns incorrect values directly
  // after resume, and does not return correct values until about 1-2 seconds
  // later. Remove this function once the bug is resolved.
  //
  // This function returns true if the BluetoothAdapter is still resuming from
  // suspension, indicating that its returned presence and power values cannot
  // yet be trusted.
  bool IsBluetoothAdapterRecoveringFromSuspend() const;

  // Called once BluetoothAdapter has recovered after resuming from suspend,
  // meaning its presence and power values can be trusted again. This method
  // checks if Bluetooth is enabled; if it is not, it cancels the initial scan.
  void OnBluetoothAdapterPresentAndPoweredChanged();

  // If the RemoteDeviceLifeCycle is available, ensure it is started (but only
  // if Bluetooth is available).
  void AttemptToStartRemoteDeviceLifecycle();

  // Returns the current state for the Smart Lock UI.
  ash::SmartLockState GetSmartLockState();

  // 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 if the "initial scan" is in progress. This state factors into what is
  // shown to the user. See |is_performing_initial_scan_| for more.
  void SetIsPerformingInitialScan(bool is_performing_initial_scan);

  // Accepts or rejects the current auth attempt according to |error|. Accepts
  // if and only if |error| is empty. If the auth attempt is accepted, unlocks
  // the screen.
  void FinalizeAuthAttempt(
      const std::optional<
          SmartLockMetricsRecorder::SmartLockAuthResultFailureReason>& error);

  // Failed to create a connection to the host during the "initial scan". See
  // |is_performing_initial_scan_| for more.
  void OnInitialScanTimeout();

  // Returns the screen lock state corresponding to the given remote |status|
  // update.
  RemoteScreenlockState GetScreenlockStateFromRemoteUpdate(
      RemoteStatusUpdate update);

  // Returns the Messenger instance associated with |life_cycle_|. This function
  // will return nullptr if |life_cycle_| is not set or the remote device is not
  // yet authenticated.
  Messenger* GetMessenger();

  // Records UMA performance metrics for the first remote status (regardless of
  // whether it's unlockable) being received.
  void RecordFirstRemoteStatusReceived(bool unlockable);

  // Records a UMA metric for the first status shown to the user as well
  // as performance metrics for how long it takes to show that first status
  // (regardless of whether it's unlockable/green).
  void RecordFirstStatusShownToUser(ash::SmartLockState new_state);

  // Clears the timers for beginning a scan and fetching remote status.
  void ResetPerformanceMetricsTimestamps();

  void SetBluetoothSuspensionRecoveryTimerForTesting(
      std::unique_ptr<base::OneShotTimer> timer);

  // For recording metrics.
  void RecordGetRemoteStatusResultSuccess(bool success = true);
  void RecordGetRemoteStatusResultFailure(
      GetRemoteStatusResultFailureReason failure_reason);
  std::string GetRemoteStatusResultFailureReasonToString(
      GetRemoteStatusResultFailureReason reason);

  // Used to call into the embedder. Expected to outlive |this| instance.
  raw_ptr<ProximityAuthClient> proximity_auth_client_;

  // Starts running after resuming from suspension, and fires once enough time
  // has elapsed such that the BluetoothAdapter's presence and power values can
  // be trusted again. To be removed once https://crbug.com/986896 is fixed.
  std::unique_ptr<base::OneShotTimer> bluetooth_suspension_recovery_timer_;

  // The Bluetooth adapter. Null if there is no adapter present on the local
  // device.
  scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;

  // Tracks whether the remote device is currently in close enough proximity to
  // the local device to allow unlocking.
  std::unique_ptr<ProximityMonitor> proximity_monitor_;

  // Whether the user is present at the remote device. Unset if no remote status
  // update has yet been received.
  std::unique_ptr<RemoteScreenlockState> remote_screenlock_state_;

  // Controls the proximity auth flow logic for a remote device. Not owned, and
  // expcted to outlive |this| instance.
  raw_ptr<RemoteDeviceLifeCycle> life_cycle_ = nullptr;

  // 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_ = false;

  // If true, either the lock screen was just shown (after resuming from
  // suspend, or directly locking the screen), or the focused user pod was
  // switched. It becomes false if the phone is found, something goes wrong
  // while searching for the phone, or the initial scan times out (at which
  // point the user visually sees an indication that the phone cannot be found).
  // Though this field becomes false after this timeout, Smart Lock continues
  // to scan for the phone until the user unlocks the screen.
  bool is_performing_initial_scan_ = false;

  // True if a secure connection is currently active with the host.
  bool is_bluetooth_connection_to_phone_active_ = false;

  // TODO(crbug.com/986896): For a short time window after resuming from
  // suspension, BluetoothAdapter returns incorrect presence and power values.
  // This field acts as a cache in case we need to check those values during
  // that time window when the device resumes. Remove this field once the bug
  // is fixed.
  bool was_bluetooth_present_and_powered_before_last_suspend_ = false;

  // True only if the remote device has responded with a remote status, either
  // "unlockable" or otherwise.
  bool has_received_first_remote_status_ = false;

  // True only if the user has been shown a Smart Lock status and tooltip,
  // either "unlockable" (green) or otherwise (yellow).
  bool has_user_been_shown_first_status_ = false;

  // The state of the current screen lock UI.
  ash::SmartLockState smartlock_state_ = ash::SmartLockState::kInactive;

  // The timestamp of when the lock or login screen is shown to the user. Begins
  // when the screen is locked, the system is rebooted, the clamshell lid is
  // opened, or another user pod is switched to on the login screen.
  base::Time show_lock_screen_time_;

  // The timestamp of when UnlockManager begins to perform the initial scan for
  // the requested remote device of the provided RemoteDeviceLifeCycle. Usually
  // begins right after |show_lock_screen_time_|, unless Bluetooth is disabled.
  // If Bluetooth is re-enabled, it also begins.
  base::Time initial_scan_start_time_;

  // The timestamp of when UnlockManager successfully establishes a secure
  // connection to the requested remote device of the provided
  // RemoteDeviceLifeCycle, and begins to try to fetch its "remote status".
  base::Time attempt_get_remote_status_start_time_;

  // Stores the last value emitted to the
  // SmartLock.GetRemoteStatus.Unlock(.Failure) metrics. Should be |nullopt|
  // until the first time GetRemoteStatus succeeds or fails.
  std::optional<bool> get_remote_status_unlock_success_;
  std::optional<GetRemoteStatusResultFailureReason>
      get_remote_status_unlock_failure_reason_;

  // Used to track if the "initial scan" has timed out. See
  // |is_performing_initial_scan_| for more.
  base::WeakPtrFactory<UnlockManagerImpl>
      initial_scan_timeout_weak_ptr_factory_{this};

  // 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<UnlockManagerImpl> reject_auth_attempt_weak_ptr_factory_{
      this};

  // Used to vend all other weak pointers.
  base::WeakPtrFactory<UnlockManagerImpl> weak_ptr_factory_{this};
};

}  // namespace proximity_auth

#endif  // CHROMEOS_ASH_COMPONENTS_PROXIMITY_AUTH_UNLOCK_MANAGER_IMPL_H_