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

// 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.

#include "chromeos/ash/components/proximity_auth/unlock_manager_impl.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/multidevice/remote_device_ref.h"
#include "chromeos/ash/components/proximity_auth/messenger.h"
#include "chromeos/ash/components/proximity_auth/metrics.h"
#include "chromeos/ash/components/proximity_auth/proximity_auth_client.h"
#include "chromeos/ash/components/proximity_auth/proximity_monitor_impl.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/client_channel.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"

namespace proximity_auth {
namespace {

using SmartLockState = ash::SmartLockState;

// 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 FindAndConnectToHostResult {
  kFoundAndConnectedToHost = 0,
  kCanceledBluetoothDisabled = 1,
  kCanceledUserEnteredPassword = 2,
  kSecureChannelConnectionAttemptFailure = 3,
  kTimedOut = 4,
  kMaxValue = kTimedOut
};

// The maximum amount of time that the unlock manager can stay in the 'waking
// up' state after resuming from sleep.
constexpr base::TimeDelta kWakingUpDuration = base::Seconds(15);

// The maximum amount of time that we wait for the BluetoothAdapter to be
// fully initialized after resuming from sleep.
// TODO(crbug.com/986896): This is necessary because the BluetoothAdapter
// returns incorrect presence and power values directly after resume, and does
// not return correct values until about 1-2 seconds later. Remove this once
// the bug is fixed.
constexpr base::TimeDelta kBluetoothAdapterResumeMaxDuration = base::Seconds(3);

// The limit 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.
constexpr base::TimeDelta kAuthAttemptTimeout = base::Seconds(5);

constexpr base::TimeDelta kMinExtendedDuration = base::Milliseconds(1);
constexpr base::TimeDelta kMaxExtendedDuration = base::Seconds(15);
const int kNumDurationMetricBuckets = 100;

const char kGetRemoteStatusNone[] = "none";
const char kGetRemoteStatusSuccess[] = "success";

// The subset of SmartLockStates that represent the first non-trival status
// shown to the user. Entries persisted to UMA histograms; do not reorder or
// delete enum values.
enum class FirstSmartLockStatus {
  kBluetoothDisabled = 0,
  kPhoneNotLockable = 1,
  kPhoneNotFound = 2,
  kPhoneNotAuthenticated = 3,
  kPhoneFoundLockedAndDistant = 4,
  kPhoneFoundLockedAndProximate = 5,
  kPhoneFoundUnlockedAndDistant = 6,
  kPhoneAuthenticated = 7,
  kPrimaryUserAbsent = 8,
  kMaxValue = kPrimaryUserAbsent
};

std::optional<FirstSmartLockStatus> GetFirstSmartLockStatus(
    SmartLockState state) {
  switch (state) {
    case SmartLockState::kBluetoothDisabled:
      return FirstSmartLockStatus::kBluetoothDisabled;
    case SmartLockState::kPhoneNotLockable:
      return FirstSmartLockStatus::kPhoneNotLockable;
    case SmartLockState::kPhoneNotFound:
      return FirstSmartLockStatus::kPhoneNotFound;
    case SmartLockState::kPhoneNotAuthenticated:
      return FirstSmartLockStatus::kPhoneNotAuthenticated;
    case SmartLockState::kPhoneFoundLockedAndDistant:
      return FirstSmartLockStatus::kPhoneFoundLockedAndDistant;
    case SmartLockState::kPhoneFoundLockedAndProximate:
      return FirstSmartLockStatus::kPhoneFoundLockedAndProximate;
    case SmartLockState::kPhoneFoundUnlockedAndDistant:
      return FirstSmartLockStatus::kPhoneFoundUnlockedAndDistant;
    case SmartLockState::kPhoneAuthenticated:
      return FirstSmartLockStatus::kPhoneAuthenticated;
    case SmartLockState::kPrimaryUserAbsent:
      return FirstSmartLockStatus::kPrimaryUserAbsent;
    default:
      return std::nullopt;
  }
}

// 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_IN_MIGRATION();
  return metrics::RemoteSecuritySettingsState::UNKNOWN;
}

std::string GetHistogramStatusSuffix(bool unlockable) {
  return unlockable ? "Unlockable" : "Other";
}

void RecordFindAndConnectToHostResult(FindAndConnectToHostResult result) {
  base::UmaHistogramEnumeration("SmartLock.FindAndConnectToHostResult.Unlock",
                                result);
}

void RecordAuthResultFailure(
    SmartLockMetricsRecorder::SmartLockAuthResultFailureReason failure_reason) {
  SmartLockMetricsRecorder::RecordAuthResultUnlockFailure(failure_reason);
}

void RecordExtendedDurationTimerMetric(const std::string& histogram_name,
                                       base::TimeDelta duration) {
  // Use a custom |max| to account for Smart Lock's timeout (larger than the
  // default 10 seconds).
  base::UmaHistogramCustomTimes(
      histogram_name, duration, kMinExtendedDuration /* min */,
      kMaxExtendedDuration /* max */, kNumDurationMetricBuckets /* buckets */);
}

bool HasCommunicatedWithPhone(SmartLockState state) {
  switch (state) {
    case SmartLockState::kDisabled:
      [[fallthrough]];
    case SmartLockState::kInactive:
      [[fallthrough]];
    case SmartLockState::kBluetoothDisabled:
      [[fallthrough]];
    case SmartLockState::kPhoneNotFound:
      [[fallthrough]];
    case SmartLockState::kConnectingToPhone:
      return false;
    case SmartLockState::kPhoneNotLockable:
      [[fallthrough]];
    case SmartLockState::kPhoneNotAuthenticated:
      [[fallthrough]];
    case SmartLockState::kPhoneFoundLockedAndDistant:
      [[fallthrough]];
    case SmartLockState::kPhoneFoundLockedAndProximate:
      [[fallthrough]];
    case SmartLockState::kPhoneFoundUnlockedAndDistant:
      [[fallthrough]];
    case SmartLockState::kPhoneAuthenticated:
      [[fallthrough]];
    case SmartLockState::kPrimaryUserAbsent:
      return true;
  }
}

}  // namespace

UnlockManagerImpl::UnlockManagerImpl(ProximityAuthClient* proximity_auth_client)
    : proximity_auth_client_(proximity_auth_client),
      bluetooth_suspension_recovery_timer_(
          std::make_unique<base::OneShotTimer>()) {
  chromeos::PowerManagerClient::Get()->AddObserver(this);

  if (device::BluetoothAdapterFactory::IsBluetoothSupported()) {
    device::BluetoothAdapterFactory::Get()->GetAdapter(
        base::BindOnce(&UnlockManagerImpl::OnBluetoothAdapterInitialized,
                       weak_ptr_factory_.GetWeakPtr()));
  }
}

UnlockManagerImpl::~UnlockManagerImpl() {
  if (life_cycle_)
    life_cycle_->RemoveObserver(this);
  if (GetMessenger())
    GetMessenger()->RemoveObserver(this);
  if (proximity_monitor_)
    proximity_monitor_->RemoveObserver(this);
  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
  if (bluetooth_adapter_)
    bluetooth_adapter_->RemoveObserver(this);
}

bool UnlockManagerImpl::IsUnlockAllowed() {
  return (remote_screenlock_state_ &&
          *remote_screenlock_state_ == RemoteScreenlockState::UNLOCKED &&
          is_bluetooth_connection_to_phone_active_ && proximity_monitor_ &&
          proximity_monitor_->IsUnlockAllowed());
}

void UnlockManagerImpl::SetRemoteDeviceLifeCycle(
    RemoteDeviceLifeCycle* life_cycle) {
  PA_LOG(VERBOSE) << "Request received to change scan state to: "
                  << (life_cycle == nullptr ? "inactive" : "active") << ".";

  if (life_cycle_)
    life_cycle_->RemoveObserver(this);
  if (GetMessenger())
    GetMessenger()->RemoveObserver(this);

  life_cycle_ = life_cycle;
  if (life_cycle_) {
    life_cycle_->AddObserver(this);

    is_bluetooth_connection_to_phone_active_ = false;
    show_lock_screen_time_ = base::DefaultClock::GetInstance()->Now();
    has_user_been_shown_first_status_ = false;

    if (IsBluetoothPresentAndPowered()) {
      SetIsPerformingInitialScan(true /* is_performing_initial_scan */);
      AttemptToStartRemoteDeviceLifecycle();
    } else {
      RecordFindAndConnectToHostResult(
          FindAndConnectToHostResult::kCanceledBluetoothDisabled);
      SetIsPerformingInitialScan(false /* is_performing_initial_scan */);
    }
  } else {
    ResetPerformanceMetricsTimestamps();

    if (proximity_monitor_)
      proximity_monitor_->RemoveObserver(this);
    proximity_monitor_.reset();

    UpdateLockScreen();
  }
}

void UnlockManagerImpl::OnLifeCycleStateChanged(
    RemoteDeviceLifeCycle::State old_state,
    RemoteDeviceLifeCycle::State new_state) {
  remote_screenlock_state_.reset();
  if (new_state == RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED) {
    DCHECK(life_cycle_->GetChannel());
    DCHECK(GetMessenger());
    if (!proximity_monitor_) {
      proximity_monitor_ = CreateProximityMonitor(life_cycle_);
      proximity_monitor_->AddObserver(this);
      proximity_monitor_->Start();
    }
    GetMessenger()->AddObserver(this);

    is_bluetooth_connection_to_phone_active_ = true;
    attempt_get_remote_status_start_time_ =
        base::DefaultClock::GetInstance()->Now();

    PA_LOG(VERBOSE) << "Successfully connected to host; waiting for remote "
                       "status update.";

    if (is_performing_initial_scan_) {
      RecordFindAndConnectToHostResult(
          FindAndConnectToHostResult::kFoundAndConnectedToHost);
    }
  } else {
    is_bluetooth_connection_to_phone_active_ = false;

    if (proximity_monitor_) {
      proximity_monitor_->RemoveObserver(this);
      proximity_monitor_->Stop();
      proximity_monitor_.reset();
    }
  }

  // Note: though the name is AUTHENTICATION_FAILED, this state actually
  // encompasses any connection failure in
  // `ash::secure_channel::mojom::ConnectionAttemptFailureReason` beside
  // Bluetooth becoming disabled. See https://crbug.com/991644 for more.
  if (new_state == RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED) {
    PA_LOG(ERROR) << "Connection attempt to host failed.";

    if (is_performing_initial_scan_) {
      RecordFindAndConnectToHostResult(
          FindAndConnectToHostResult::kSecureChannelConnectionAttemptFailure);
      SetIsPerformingInitialScan(false /* is_performing_initial_scan */);
    }
  }

  if (new_state == RemoteDeviceLifeCycle::State::FINDING_CONNECTION &&
      old_state == RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED) {
    PA_LOG(ERROR) << "Secure channel dropped for unknown reason; potentially "
                     "due to Bluetooth being disabled.";

    if (is_performing_initial_scan_) {
      OnDisconnected();
      SetIsPerformingInitialScan(false /* is_performing_initial_scan */);
    }
  }

  UpdateLockScreen();
}

void UnlockManagerImpl::OnUnlockEventSent(bool success) {
  if (!is_attempting_auth_) {
    PA_LOG(ERROR) << "Sent easy_unlock event, but no auth attempted.";
    FinalizeAuthAttempt(
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
            kUnlockEventSentButNotAttemptingAuth);
  } else if (success) {
    FinalizeAuthAttempt(std::nullopt /* failure_reason */);
  } else {
    FinalizeAuthAttempt(
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
            kFailedtoNotifyHostDeviceThatSmartLockWasUsed);
  }
}

void UnlockManagerImpl::OnRemoteStatusUpdate(
    const RemoteStatusUpdate& status_update) {
  PA_LOG(VERBOSE) << "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_ = std::make_unique<RemoteScreenlockState>(
      GetScreenlockStateFromRemoteUpdate(status_update));

  // Only record these metrics within the initial period of opening the laptop
  // displaying the lock screen.
  if (is_performing_initial_scan_) {
    RecordFirstRemoteStatusReceived(
        *remote_screenlock_state_ ==
        RemoteScreenlockState::UNLOCKED /* unlockable */);
  }

  // This also calls |UpdateLockScreen()|
  SetIsPerformingInitialScan(false /* is_performing_initial_scan */);
}

void UnlockManagerImpl::OnUnlockResponse(bool success) {
  if (!is_attempting_auth_) {
    FinalizeAuthAttempt(
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
            kUnlockRequestSentButNotAttemptingAuth);
    PA_LOG(ERROR) << "Unlock request sent but not attempting auth.";
    return;
  }

  PA_LOG(INFO) << "Received unlock response from device: "
               << (success ? "yes" : "no") << ".";

  if (success && GetMessenger()) {
    GetMessenger()->DispatchUnlockEvent();
  } else {
    FinalizeAuthAttempt(
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
            kFailedToSendUnlockRequest);
  }
}

void UnlockManagerImpl::OnDisconnected() {
  if (is_attempting_auth_) {
    RecordAuthResultFailure(
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
            kAuthenticatedChannelDropped);
  } else if (is_performing_initial_scan_) {
    RecordGetRemoteStatusResultFailure(
        GetRemoteStatusResultFailureReason::kAuthenticatedChannelDropped);
  }

  if (GetMessenger())
    GetMessenger()->RemoveObserver(this);
}

void UnlockManagerImpl::OnProximityStateChanged() {
  PA_LOG(VERBOSE) << "Proximity state changed.";
  UpdateLockScreen();
}

void UnlockManagerImpl::OnBluetoothAdapterInitialized(
    scoped_refptr<device::BluetoothAdapter> adapter) {
  bluetooth_adapter_ = adapter;
  bluetooth_adapter_->AddObserver(this);
}

void UnlockManagerImpl::AdapterPresentChanged(device::BluetoothAdapter* adapter,
                                              bool present) {
  if (!IsBluetoothAdapterRecoveringFromSuspend())
    OnBluetoothAdapterPresentAndPoweredChanged();
}

void UnlockManagerImpl::AdapterPoweredChanged(device::BluetoothAdapter* adapter,
                                              bool powered) {
  if (!IsBluetoothAdapterRecoveringFromSuspend())
    OnBluetoothAdapterPresentAndPoweredChanged();
}

void UnlockManagerImpl::SuspendImminent(
    power_manager::SuspendImminent::Reason reason) {
  // TODO(crbug.com/986896): For a short time window after resuming from
  // suspension, BluetoothAdapter returns incorrect presence and power values.
  // Cache the correct values now, in case we need to check those values during
  // that time window when the device resumes.
  was_bluetooth_present_and_powered_before_last_suspend_ =
      IsBluetoothPresentAndPowered();
  bluetooth_suspension_recovery_timer_->Stop();
}

void UnlockManagerImpl::SuspendDone(base::TimeDelta sleep_duration) {
  bluetooth_suspension_recovery_timer_->Start(
      FROM_HERE, kBluetoothAdapterResumeMaxDuration,
      base::BindOnce(
          &UnlockManagerImpl::OnBluetoothAdapterPresentAndPoweredChanged,
          weak_ptr_factory_.GetWeakPtr()));

  // The next scan after resuming is expected to be triggered by calling
  // SetRemoteDeviceLifeCycle().
}

bool UnlockManagerImpl::IsBluetoothPresentAndPowered() const {
  // TODO(crbug.com/986896): If the BluetoothAdapter is still "resuming after
  // suspension" at this time, it's prone to this bug, meaning we cannot trust
  // its returned presence and power values. If this is the case, depend on
  // the cached |was_bluetooth_present_and_powered_before_last_suspend_| to
  // signal if Bluetooth is enabled; otherwise, directly check request values
  // from BluetoothAdapter. Remove this check once the bug is fixed.
  if (IsBluetoothAdapterRecoveringFromSuspend())
    return was_bluetooth_present_and_powered_before_last_suspend_;

  return bluetooth_adapter_ && bluetooth_adapter_->IsPresent() &&
         bluetooth_adapter_->IsPowered();
}

void UnlockManagerImpl::OnBluetoothAdapterPresentAndPoweredChanged() {
  DCHECK(!IsBluetoothAdapterRecoveringFromSuspend());

  if (IsBluetoothPresentAndPowered()) {
    if (!is_performing_initial_scan_ &&
        !is_bluetooth_connection_to_phone_active_) {
      SetIsPerformingInitialScan(true /* is_performing_initial_scan */);
    }
    return;
  }

  if (is_performing_initial_scan_) {
    if (is_bluetooth_connection_to_phone_active_ &&
        !has_received_first_remote_status_) {
      RecordGetRemoteStatusResultFailure(
          GetRemoteStatusResultFailureReason::kCanceledBluetoothDisabled);
    } else {
      RecordFindAndConnectToHostResult(
          FindAndConnectToHostResult::kCanceledBluetoothDisabled);
    }

    SetIsPerformingInitialScan(false /* is_performing_initial_scan */);
    return;
  }

  // If Bluetooth is off but no initial scan is active, still ensure that the
  // lock screen UI reflects that Bluetooth is off.
  UpdateLockScreen();
}

bool UnlockManagerImpl::IsBluetoothAdapterRecoveringFromSuspend() const {
  return bluetooth_suspension_recovery_timer_->IsRunning();
}

void UnlockManagerImpl::AttemptToStartRemoteDeviceLifecycle() {
  if (IsBluetoothPresentAndPowered() && life_cycle_ &&
      life_cycle_->GetState() == RemoteDeviceLifeCycle::State::STOPPED) {
    // If Bluetooth is disabled after this, |life_cycle_| will be notified by
    // SecureChannel that the connection attempt failed. From that point on,
    // |life_cycle_| will wait to be started again by UnlockManager.
    life_cycle_->Start();
  }
}

void UnlockManagerImpl::OnAuthAttempted(mojom::AuthType auth_type) {
  if (is_attempting_auth_) {
    PA_LOG(VERBOSE) << "Already attempting auth.";
    return;
  }

  if (auth_type != mojom::AuthType::USER_CLICK)
    return;

  is_attempting_auth_ = true;

  if (!life_cycle_ || !GetMessenger()) {
    PA_LOG(ERROR) << "No life_cycle active when auth was attempted";
    FinalizeAuthAttempt(
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
            kNoPendingOrActiveHost);
    UpdateLockScreen();
    return;
  }

  if (!IsUnlockAllowed()) {
    FinalizeAuthAttempt(
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
            kUnlockNotAllowed);
    UpdateLockScreen();
    return;
  }

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(
          &UnlockManagerImpl::FinalizeAuthAttempt,
          reject_auth_attempt_weak_ptr_factory_.GetWeakPtr(),
          SmartLockMetricsRecorder::SmartLockAuthResultFailureReason::
              kAuthAttemptTimedOut),
      kAuthAttemptTimeout);

  GetMessenger()->RequestUnlock();
}

void UnlockManagerImpl::CancelConnectionAttempt() {
  PA_LOG(VERBOSE) << "User entered password.";

  bluetooth_suspension_recovery_timer_->Stop();

  // Note: There is no need to record metrics here if Bluetooth isn't present
  // and powered; that has already been handled at this point in
  // OnBluetoothAdapterPresentAndPoweredChanged().
  if (!IsBluetoothPresentAndPowered())
    return;

  if (is_performing_initial_scan_) {
    if (is_bluetooth_connection_to_phone_active_ &&
        !has_received_first_remote_status_) {
      RecordGetRemoteStatusResultFailure(
          GetRemoteStatusResultFailureReason::kCanceledUserEnteredPassword);
    } else {
      RecordFindAndConnectToHostResult(
          FindAndConnectToHostResult::kCanceledUserEnteredPassword);
    }

    SetIsPerformingInitialScan(false /* is_performing_initial_scan */);
  }
}

std::unique_ptr<ProximityMonitor> UnlockManagerImpl::CreateProximityMonitor(
    RemoteDeviceLifeCycle* life_cycle) {
  return std::make_unique<ProximityMonitorImpl>(life_cycle->GetRemoteDevice(),
                                                life_cycle->GetChannel());
}

SmartLockState UnlockManagerImpl::GetSmartLockState() {
  if (!life_cycle_)
    return SmartLockState::kInactive;

  if (!IsBluetoothPresentAndPowered())
    return SmartLockState::kBluetoothDisabled;

  if (IsUnlockAllowed())
    return SmartLockState::kPhoneAuthenticated;

  RemoteDeviceLifeCycle::State life_cycle_state = life_cycle_->GetState();
  if (life_cycle_state == RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED)
    return SmartLockState::kPhoneNotAuthenticated;

  if (is_performing_initial_scan_)
    return SmartLockState::kConnectingToPhone;

  Messenger* messenger = GetMessenger();

  // Show a timeout state if we can not connect to the remote device in a
  // reasonable amount of time.
  if (!messenger)
    return SmartLockState::kPhoneNotFound;

  // If the RSSI is too low, then the remote device is nowhere near the local
  // device. This message should take priority over messages about Smart Lock
  // states.
  if (proximity_monitor_ && !proximity_monitor_->IsUnlockAllowed()) {
    if (remote_screenlock_state_ &&
        *remote_screenlock_state_ == RemoteScreenlockState::UNLOCKED) {
      return SmartLockState::kPhoneFoundUnlockedAndDistant;
    } else {
      return SmartLockState::kPhoneFoundLockedAndDistant;
    }
  }

  if (remote_screenlock_state_) {
    switch (*remote_screenlock_state_) {
      case RemoteScreenlockState::DISABLED:
        return SmartLockState::kPhoneNotLockable;

      case RemoteScreenlockState::LOCKED:
        return SmartLockState::kPhoneFoundLockedAndProximate;

      case RemoteScreenlockState::PRIMARY_USER_ABSENT:
        return SmartLockState::kPrimaryUserAbsent;

      case RemoteScreenlockState::UNKNOWN:
      case RemoteScreenlockState::UNLOCKED:
        // Handled by the code below.
        break;
    }
  }

  if (messenger) {
    PA_LOG(WARNING) << "Connection to host established, but remote screenlock "
                    << "state was either malformed or not received.";
  }

  // TODO(crbug.com/1233587): Add more granular error states
  return SmartLockState::kPhoneNotFound;
}

void UnlockManagerImpl::UpdateLockScreen() {
  AttemptToStartRemoteDeviceLifecycle();

  SmartLockState new_state = GetSmartLockState();
  if (smartlock_state_ == new_state)
    return;

  PA_LOG(INFO) << "Updating Smart Lock state from " << smartlock_state_
               << " to " << new_state;

  RecordFirstStatusShownToUser(new_state);

  proximity_auth_client_->UpdateSmartLockState(new_state);
  smartlock_state_ = new_state;
}

void UnlockManagerImpl::SetIsPerformingInitialScan(
    bool is_performing_initial_scan) {
  PA_LOG(VERBOSE) << "Initial scan state is ["
                  << (is_performing_initial_scan_ ? "active" : "inactive")
                  << "]. Requesting state ["
                  << (is_performing_initial_scan ? "active" : "inactive")
                  << "].";

  is_performing_initial_scan_ = is_performing_initial_scan;

  // Clear the waking up state after a timeout.
  initial_scan_timeout_weak_ptr_factory_.InvalidateWeakPtrs();
  if (is_performing_initial_scan_) {
    initial_scan_start_time_ = base::DefaultClock::GetInstance()->Now();
    has_received_first_remote_status_ = false;

    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&UnlockManagerImpl::OnInitialScanTimeout,
                       initial_scan_timeout_weak_ptr_factory_.GetWeakPtr()),
        kWakingUpDuration);
  }

  UpdateLockScreen();
}

void UnlockManagerImpl::OnInitialScanTimeout() {
  // Note: There is no need to record metrics here if Bluetooth isn't present
  // and powered; that has already been handled at this point in
  // OnBluetoothAdapterPresentAndPoweredChanged().
  if (!IsBluetoothPresentAndPowered())
    return;

  if (is_bluetooth_connection_to_phone_active_) {
    PA_LOG(ERROR) << "Successfully connected to host, but it did not provide "
                     "remote status update.";
    RecordGetRemoteStatusResultFailure(
        GetRemoteStatusResultFailureReason::
            kTimedOutDidNotReceiveRemoteStatusUpdate);
  } else {
    PA_LOG(INFO) << "Initial scan for host returned no result.";
    RecordFindAndConnectToHostResult(FindAndConnectToHostResult::kTimedOut);
  }

  SetIsPerformingInitialScan(false /* is_performing_initial_scan */);
}

void UnlockManagerImpl::FinalizeAuthAttempt(
    const std::optional<
        SmartLockMetricsRecorder::SmartLockAuthResultFailureReason>& error) {
  if (error) {
    RecordAuthResultFailure(*error);
  }

  if (!is_attempting_auth_)
    return;

  // Cancel the pending task to time out the auth attempt.
  reject_auth_attempt_weak_ptr_factory_.InvalidateWeakPtrs();

  bool should_accept = !error;
  if (should_accept && proximity_monitor_)
    proximity_monitor_->RecordProximityMetricsOnAuthSuccess();

  is_attempting_auth_ = false;

  PA_LOG(VERBOSE) << "Finalizing unlock...";
  proximity_auth_client_->FinalizeUnlock(should_accept);
}

UnlockManagerImpl::RemoteScreenlockState
UnlockManagerImpl::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_PRESENCE_SECONDARY ||
          update.user_presence == USER_PRESENCE_BACKGROUND) {
        return RemoteScreenlockState::PRIMARY_USER_ABSENT;
      }

      if (update.user_presence == USER_PRESENT)
        return RemoteScreenlockState::UNLOCKED;

      return RemoteScreenlockState::LOCKED;

    case SECURE_SCREEN_LOCK_STATE_UNKNOWN:
      return RemoteScreenlockState::UNKNOWN;
  }

  NOTREACHED_IN_MIGRATION();
  return RemoteScreenlockState::UNKNOWN;
}

Messenger* UnlockManagerImpl::GetMessenger() {
  // TODO(tengs): We should use a weak pointer to hold the Messenger instance
  // instead.
  if (!life_cycle_)
    return nullptr;
  return life_cycle_->GetMessenger();
}

void UnlockManagerImpl::RecordFirstRemoteStatusReceived(bool unlockable) {
  if (has_received_first_remote_status_)
    return;
  has_received_first_remote_status_ = true;

  RecordGetRemoteStatusResultSuccess();

  if (initial_scan_start_time_.is_null() ||
      attempt_get_remote_status_start_time_.is_null()) {
    PA_LOG(WARNING) << "Attempted to RecordFirstRemoteStatusReceived() "
                       "without initial timestamps recorded.";
    NOTREACHED_IN_MIGRATION();
    return;
  }

  const std::string histogram_status_suffix =
      GetHistogramStatusSuffix(unlockable);

  base::Time now = base::DefaultClock::GetInstance()->Now();
  base::TimeDelta start_scan_to_receive_first_remote_status_duration =
      now - initial_scan_start_time_;
  base::TimeDelta authentication_to_receive_first_remote_status_duration =
      now - attempt_get_remote_status_start_time_;

  RecordExtendedDurationTimerMetric(
      "SmartLock.Performance.StartScanToReceiveFirstRemoteStatusDuration."
      "Unlock",
      start_scan_to_receive_first_remote_status_duration);
  RecordExtendedDurationTimerMetric(
      "SmartLock.Performance.StartScanToReceiveFirstRemoteStatusDuration."
      "Unlock." +
          histogram_status_suffix,
      start_scan_to_receive_first_remote_status_duration);

  // This should be much less than 10 seconds, so use UmaHistogramTimes.
  base::UmaHistogramTimes(
      "SmartLock.Performance."
      "AuthenticationToReceiveFirstRemoteStatusDuration.Unlock",
      authentication_to_receive_first_remote_status_duration);
  base::UmaHistogramTimes(
      "SmartLock.Performance."
      "AuthenticationToReceiveFirstRemoteStatusDuration.Unlock." +
          histogram_status_suffix,
      authentication_to_receive_first_remote_status_duration);
}

void UnlockManagerImpl::RecordFirstStatusShownToUser(SmartLockState new_state) {
  std::optional<FirstSmartLockStatus> first_status =
      GetFirstSmartLockStatus(new_state);
  if (!first_status.has_value()) {
    return;
  }

  if (has_user_been_shown_first_status_) {
    return;
  }
  has_user_been_shown_first_status_ = true;

  if (show_lock_screen_time_.is_null()) {
    PA_LOG(WARNING) << "Attempted to RecordFirstStatusShownToUser() "
                       "without initial timestamp recorded.";
    NOTREACHED_IN_MIGRATION();
    return;
  }

  base::UmaHistogramEnumeration("SmartLock.FirstStatusToUser",
                                first_status.value());

  base::Time now = base::DefaultClock::GetInstance()->Now();
  base::TimeDelta show_lock_screen_to_show_first_status_to_user_duration =
      now - show_lock_screen_time_;

    RecordExtendedDurationTimerMetric(
        "SmartLock.Performance.ShowLockScreenToShowFirstStatusToUserDuration."
        "Unlock",
        show_lock_screen_to_show_first_status_to_user_duration);

    if (new_state == SmartLockState::kPhoneAuthenticated) {
      RecordExtendedDurationTimerMetric(
          "SmartLock.Performance.ShowLockScreenToShowFirstStatusToUserDuration."
          "Unlock.Unlockable",
          show_lock_screen_to_show_first_status_to_user_duration);
    } else if (HasCommunicatedWithPhone(new_state)) {
      // Only log to Unlock.Other if we aren't in an unlockable state since
      // that's covered by the other metric, and only if we are in a state that
      // indicates we were able to communicate with the phone over Bluetooth
      // since in all other cases the time to show the first status is highly
      // deterministic.
      RecordExtendedDurationTimerMetric(
          "SmartLock.Performance.ShowLockScreenToShowFirstStatusToUserDuration."
          "Unlock.Other",
          show_lock_screen_to_show_first_status_to_user_duration);
    }
}

void UnlockManagerImpl::ResetPerformanceMetricsTimestamps() {
  show_lock_screen_time_ = base::Time();
  initial_scan_start_time_ = base::Time();
  attempt_get_remote_status_start_time_ = base::Time();
}

void UnlockManagerImpl::SetBluetoothSuspensionRecoveryTimerForTesting(
    std::unique_ptr<base::OneShotTimer> timer) {
  bluetooth_suspension_recovery_timer_ = std::move(timer);
}

void UnlockManagerImpl::RecordGetRemoteStatusResultSuccess(bool success) {
  base::UmaHistogramBoolean("SmartLock.GetRemoteStatus.Unlock", success);
  get_remote_status_unlock_success_ = success;
}

void UnlockManagerImpl::RecordGetRemoteStatusResultFailure(
    GetRemoteStatusResultFailureReason failure_reason) {
  RecordGetRemoteStatusResultSuccess(false /* success */);
  base::UmaHistogramEnumeration("SmartLock.GetRemoteStatus.Unlock.Failure",
                                failure_reason);
  get_remote_status_unlock_failure_reason_ = failure_reason;
}

std::string UnlockManagerImpl::GetRemoteStatusResultFailureReasonToString(
    GetRemoteStatusResultFailureReason reason) {
  switch (reason) {
    case GetRemoteStatusResultFailureReason::kCanceledBluetoothDisabled:
      return "CanceledBluetoothDisabled";
    case GetRemoteStatusResultFailureReason::
        kDeprecatedTimedOutCouldNotEstablishAuthenticatedChannel:
      return "DeprecatedTimedOutCouldNotEstablishAuthenticatedChannel";
    case GetRemoteStatusResultFailureReason::
        kTimedOutDidNotReceiveRemoteStatusUpdate:
      return "TimedOutDidNotReceiveRemoteStatusUpdate";
    case GetRemoteStatusResultFailureReason::
        kDeprecatedUserEnteredPasswordWhileBluetoothDisabled:
      return "DeprecatedUserEnteredPasswordWhileBluetoothDisabled";
    case GetRemoteStatusResultFailureReason::kCanceledUserEnteredPassword:
      return "CanceledUserEnteredPassword";
    case GetRemoteStatusResultFailureReason::kAuthenticatedChannelDropped:
      return "AuthenticatedChannelDropped";
  }
}

std::string UnlockManagerImpl::GetLastRemoteStatusUnlockForLogging() {
  if (!get_remote_status_unlock_success_.has_value()) {
    return kGetRemoteStatusNone;
  }

  if (*get_remote_status_unlock_success_) {
    return kGetRemoteStatusSuccess;
  }

  if (!get_remote_status_unlock_failure_reason_.has_value()) {
    return kGetRemoteStatusNone;
  }

  return GetRemoteStatusResultFailureReasonToString(
      *get_remote_status_unlock_failure_reason_);
}

}  // namespace proximity_auth