blob: 920cabd9caf19ad3a1cdaf1b2e3c2dbf9a4ff449 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/login/easy_unlock/smartlock_state_handler.h"
#include <stddef.h>
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/smartlock_state.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/ash/login/easy_unlock/easy_unlock_metrics.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
namespace ash {
namespace {
proximity_auth::ScreenlockBridge::UserPodCustomIcon GetIconForState(
SmartLockState state) {
switch (state) {
case SmartLockState::kPhoneFoundLockedAndProximate:
// The Smart Lock revamp UI needs to be able to distinguish the proximate
// case.
// TODO(crbug.com/1233614): Remove this special case once SmartLockState
// is routed directly to SmartLockAuthFactorModel.
if (base::FeatureList::IsEnabled(features::kSmartLockUIRevamp)) {
return proximity_auth::ScreenlockBridge::
USER_POD_CUSTOM_ICON_LOCKED_TO_BE_ACTIVATED;
}
[[fallthrough]];
case SmartLockState::kBluetoothDisabled:
case SmartLockState::kPhoneNotFound:
case SmartLockState::kPhoneNotAuthenticated:
case SmartLockState::kPhoneNotLockable:
return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED;
case SmartLockState::kPhoneFoundUnlockedAndDistant:
case SmartLockState::kPhoneFoundLockedAndDistant:
// TODO(isherman): This icon is currently identical to the regular locked
// icon. Once the reduced proximity range flag is removed, consider
// deleting the redundant icon.
return proximity_auth::ScreenlockBridge::
USER_POD_CUSTOM_ICON_LOCKED_WITH_PROXIMITY_HINT;
case SmartLockState::kConnectingToPhone:
return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_SPINNER;
case SmartLockState::kPhoneAuthenticated:
return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_UNLOCKED;
case SmartLockState::kInactive:
case SmartLockState::kDisabled:
return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_NONE;
case SmartLockState::kPrimaryUserAbsent:
return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_LOCKED;
}
NOTREACHED();
return proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_NONE;
}
size_t GetTooltipResourceId(SmartLockState state) {
switch (state) {
case SmartLockState::kInactive:
case SmartLockState::kDisabled:
case SmartLockState::kConnectingToPhone:
case SmartLockState::kPhoneAuthenticated:
return 0;
case SmartLockState::kBluetoothDisabled:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_NO_BLUETOOTH;
case SmartLockState::kPhoneNotFound:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_NO_PHONE;
case SmartLockState::kPhoneNotAuthenticated:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_NOT_AUTHENTICATED;
case SmartLockState::kPhoneFoundLockedAndProximate:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_LOCKED;
case SmartLockState::kPhoneNotLockable:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_UNLOCKABLE;
case SmartLockState::kPhoneFoundUnlockedAndDistant:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_RSSI_TOO_LOW;
case SmartLockState::kPhoneFoundLockedAndDistant:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_LOCKED_AND_RSSI_TOO_LOW;
case SmartLockState::kPrimaryUserAbsent:
return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PRIMARY_USER_ABSENT;
}
NOTREACHED();
return 0;
}
bool TooltipContainsDeviceType(SmartLockState state) {
return (state == SmartLockState::kPhoneAuthenticated ||
state == SmartLockState::kPhoneNotLockable ||
state == SmartLockState::kBluetoothDisabled ||
state == SmartLockState::kPhoneFoundUnlockedAndDistant ||
state == SmartLockState::kPhoneFoundLockedAndDistant);
}
// Returns true iff the `state` corresponds to a locked remote device.
bool IsLockedState(SmartLockState state) {
return (state == SmartLockState::kPhoneFoundLockedAndProximate ||
state == SmartLockState::kPhoneFoundLockedAndDistant);
}
} // namespace
SmartLockStateHandler::SmartLockStateHandler(
const AccountId& account_id,
proximity_auth::ScreenlockBridge* screenlock_bridge)
: state_(SmartLockState::kInactive),
account_id_(account_id),
screenlock_bridge_(screenlock_bridge) {
DCHECK(screenlock_bridge_);
screenlock_bridge_->AddObserver(this);
}
SmartLockStateHandler::~SmartLockStateHandler() {
screenlock_bridge_->RemoveObserver(this);
// Make sure the Smart Lock state set by this gets cleared.
ChangeState(SmartLockState::kInactive);
}
bool SmartLockStateHandler::IsActive() const {
return state_ != SmartLockState::kInactive;
}
bool SmartLockStateHandler::InStateValidOnRemoteAuthFailure() const {
// Note that NO_PHONE is not valid in this case because the phone may close
// the connection if the auth challenge sent to it is invalid. This case
// should be handled as authentication failure.
return state_ == SmartLockState::kInactive ||
state_ == SmartLockState::kDisabled ||
state_ == SmartLockState::kBluetoothDisabled ||
state_ == SmartLockState::kPhoneFoundLockedAndProximate;
}
void SmartLockStateHandler::ChangeState(SmartLockState new_state) {
if (state_ == new_state)
return;
state_ = new_state;
// If lock screen is not active or it forces offline password, just cache the
// current state. The screenlock state will get refreshed in `ScreenDidLock`.
if (!screenlock_bridge_->IsLocked())
return;
// Do nothing when auth type is online.
if (screenlock_bridge_->lock_handler()->GetAuthType(account_id_) ==
proximity_auth::mojom::AuthType::ONLINE_SIGN_IN) {
return;
}
if (IsLockedState(state_))
did_see_locked_phone_ = true;
UpdateScreenlockAuthType();
// TODO(crbug.com/1233614): Return early if kSmartLockUIRevamp is enabled.
proximity_auth::ScreenlockBridge::UserPodCustomIcon icon =
GetIconForState(state_);
if (icon == proximity_auth::ScreenlockBridge::USER_POD_CUSTOM_ICON_NONE) {
screenlock_bridge_->lock_handler()->HideUserPodCustomIcon(account_id_);
return;
}
proximity_auth::ScreenlockBridge::UserPodCustomIconInfo icon_info;
icon_info.SetIcon(icon);
UpdateTooltipOptions(&icon_info);
// For states without tooltips, we still need to set an accessibility label.
if (state_ == SmartLockState::kConnectingToPhone) {
icon_info.SetAriaLabel(
l10n_util::GetStringUTF16(IDS_SMART_LOCK_SPINNER_ACCESSIBILITY_LABEL));
}
// Accessibility users may not be able to see the green icon which indicates
// the phone is authenticated. Provide message to that effect.
if (state_ == SmartLockState::kPhoneAuthenticated) {
icon_info.SetAriaLabel(l10n_util::GetStringUTF16(
IDS_SMART_LOCK_SCREENLOCK_AUTHENTICATED_LABEL));
}
screenlock_bridge_->lock_handler()->ShowUserPodCustomIcon(account_id_,
icon_info);
}
void SmartLockStateHandler::OnScreenDidLock(
proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
did_see_locked_phone_ = IsLockedState(state_);
RefreshSmartLockState();
}
void SmartLockStateHandler::OnScreenDidUnlock(
proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
// Upon a successful unlock event, record whether the user's phone was locked
// at any point while the lock screen was up.
if (state_ == SmartLockState::kPhoneAuthenticated)
RecordEasyUnlockDidUserManuallyUnlockPhone(did_see_locked_phone_);
did_see_locked_phone_ = false;
}
void SmartLockStateHandler::OnFocusedUserChanged(const AccountId& account_id) {}
void SmartLockStateHandler::RefreshSmartLockState() {
SmartLockState last_state = state_;
// This should force updating smart lock state.
state_ = SmartLockState::kInactive;
ChangeState(last_state);
}
void SmartLockStateHandler::UpdateTooltipOptions(
proximity_auth::ScreenlockBridge::UserPodCustomIconInfo* icon_info) {
size_t resource_id = 0;
std::u16string device_name;
resource_id = GetTooltipResourceId(state_);
if (TooltipContainsDeviceType(state_))
device_name = GetDeviceName();
if (!resource_id)
return;
std::u16string tooltip;
if (device_name.empty()) {
tooltip = l10n_util::GetStringUTF16(resource_id);
} else {
tooltip = l10n_util::GetStringFUTF16(resource_id, device_name);
}
if (tooltip.empty())
return;
bool autoshow_tooltip = state_ != SmartLockState::kPhoneAuthenticated;
icon_info->SetTooltip(tooltip, autoshow_tooltip);
}
std::u16string SmartLockStateHandler::GetDeviceName() {
return ui::GetChromeOSDeviceName();
}
void SmartLockStateHandler::UpdateScreenlockAuthType() {
// Do not override online signin.
const proximity_auth::mojom::AuthType existing_auth_type =
screenlock_bridge_->lock_handler()->GetAuthType(account_id_);
DCHECK_NE(proximity_auth::mojom::AuthType::ONLINE_SIGN_IN,
existing_auth_type);
if (state_ == SmartLockState::kPhoneAuthenticated) {
if (existing_auth_type != proximity_auth::mojom::AuthType::USER_CLICK) {
screenlock_bridge_->lock_handler()->SetAuthType(
account_id_, proximity_auth::mojom::AuthType::USER_CLICK,
l10n_util::GetStringUTF16(
IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE));
}
} else if (existing_auth_type !=
proximity_auth::mojom::AuthType::OFFLINE_PASSWORD) {
screenlock_bridge_->lock_handler()->SetAuthType(
account_id_, proximity_auth::mojom::AuthType::OFFLINE_PASSWORD,
std::u16string());
}
}
} // namespace ash