blob: c05b3fd8fae4af3947c34b9160ffbd20c5930b02 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/detachable_base/detachable_base_handler.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/detachable_base/detachable_base_observer.h"
#include "ash/public/cpp/session/user_info.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace ash {
namespace {
// Keys in local state used to persist detachable base pairing state.
// Example detachable base pref value:
// {
// ash.detachable_base.devices: {
// user_key_1: {
// last_used: hex encoded base device ID
// },
// user_key_2: {
// // no detachable_base device info, e.g. the user has not ever used it
// }
// }
// }
constexpr char kLastUsedByUserPrefKey[] = "last_used";
std::string GetKeyForPrefs(const AccountId& account_id) {
if (account_id.HasAccountIdKey())
return account_id.GetAccountIdKey();
return account_id.GetUserEmail();
}
} // namespace
// static
void DetachableBaseHandler::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kDetachableBaseDevices);
}
DetachableBaseHandler::DetachableBaseHandler(PrefService* local_state)
: local_state_(local_state),
hammerd_observation_(this),
power_manager_observation_(this) {
if (HammerdClient::Get()) // May be null in tests
hammerd_observation_.Observe(HammerdClient::Get());
chromeos::PowerManagerClient* power_manager_client =
chromeos::PowerManagerClient::Get();
power_manager_observation_.Observe(power_manager_client);
power_manager_client->GetSwitchStates(
base::BindOnce(&DetachableBaseHandler::OnGotPowerManagerSwitchStates,
weak_ptr_factory_.GetWeakPtr()));
if (GetPairingStatus() != DetachableBasePairingStatus::kNone)
NotifyPairingStatusChanged();
}
DetachableBaseHandler::~DetachableBaseHandler() = default;
void DetachableBaseHandler::AddObserver(DetachableBaseObserver* observer) {
observers_.AddObserver(observer);
}
void DetachableBaseHandler::RemoveObserver(DetachableBaseObserver* observer) {
observers_.RemoveObserver(observer);
}
void DetachableBaseHandler::RemoveUserData(const UserInfo& user) {
last_used_devices_.erase(user.account_id);
if (local_state_) {
ScopedDictPrefUpdate update(local_state_, prefs::kDetachableBaseDevices);
update->Remove(GetKeyForPrefs(user.account_id));
}
}
DetachableBasePairingStatus DetachableBaseHandler::GetPairingStatus() const {
if (!tablet_mode_.has_value() ||
*tablet_mode_ == chromeos::PowerManagerClient::TabletMode::ON) {
return DetachableBasePairingStatus::kNone;
}
return pairing_status_;
}
bool DetachableBaseHandler::PairedBaseMatchesLastUsedByUser(
const UserInfo& user) const {
if (GetPairingStatus() != DetachableBasePairingStatus::kAuthenticated)
return false;
DCHECK(!authenticated_base_id_.empty());
// If local state is not set for non-ephemeral users, the last used detachable
// base cannot be determined at this point.
// Assume no device has previously been used - the state will be evaluated
// again when the local state prefs get initialzized.
if (!local_state_ && !user.is_ephemeral)
return true;
DetachableBaseId last_used_base = GetLastUsedDeviceForUser(user);
return last_used_base.empty() || authenticated_base_id_ == last_used_base;
}
bool DetachableBaseHandler::SetPairedBaseAsLastUsedByUser(
const UserInfo& user) {
if (GetPairingStatus() != DetachableBasePairingStatus::kAuthenticated)
return false;
// Do not update the last paired device for non-ephemeral users if local state
// is not set, as the value would not be correctly preserved. Note that the
// observers will be notified about the pairing status change when the local
// state gets initialized - they should attempt to set this value at that
// point.
if (!local_state_ && !user.is_ephemeral)
return false;
last_used_devices_[user.account_id] = authenticated_base_id_;
if (!user.is_ephemeral) {
ScopedDictPrefUpdate update(local_state_, prefs::kDetachableBaseDevices);
update->EnsureDict(GetKeyForPrefs(user.account_id))
->Set(kLastUsedByUserPrefKey, authenticated_base_id_);
}
return true;
}
void DetachableBaseHandler::BaseFirmwareUpdateNeeded() {
NotifyBaseRequiresFirmwareUpdate(true /*requires_update*/);
}
void DetachableBaseHandler::BaseFirmwareUpdateStarted() {}
void DetachableBaseHandler::BaseFirmwareUpdateSucceeded() {
NotifyBaseRequiresFirmwareUpdate(false /*requires_update*/);
}
void DetachableBaseHandler::BaseFirmwareUpdateFailed() {}
void DetachableBaseHandler::PairChallengeSucceeded(
const std::vector<uint8_t>& base_id) {
authenticated_base_id_ = base::HexEncode(base_id);
pairing_status_ = DetachableBasePairingStatus::kAuthenticated;
if (GetPairingStatus() != DetachableBasePairingStatus::kNone)
NotifyPairingStatusChanged();
}
void DetachableBaseHandler::PairChallengeFailed() {
authenticated_base_id_.clear();
pairing_status_ = DetachableBasePairingStatus::kNotAuthenticated;
if (GetPairingStatus() != DetachableBasePairingStatus::kNone)
NotifyPairingStatusChanged();
}
void DetachableBaseHandler::InvalidBaseConnected() {
authenticated_base_id_.clear();
pairing_status_ = DetachableBasePairingStatus::kInvalidDevice;
if (GetPairingStatus() != DetachableBasePairingStatus::kNone)
NotifyPairingStatusChanged();
}
void DetachableBaseHandler::TabletModeEventReceived(
chromeos::PowerManagerClient::TabletMode mode,
base::TimeTicks timestamp) {
UpdateTabletMode(mode);
}
void DetachableBaseHandler::OnGotPowerManagerSwitchStates(
std::optional<chromeos::PowerManagerClient::SwitchStates> switch_states) {
if (!switch_states.has_value() || tablet_mode_.has_value())
return;
UpdateTabletMode(switch_states->tablet_mode);
}
void DetachableBaseHandler::UpdateTabletMode(
chromeos::PowerManagerClient::TabletMode mode) {
DetachableBasePairingStatus old_pairing_status = GetPairingStatus();
tablet_mode_ = mode;
if (*tablet_mode_ == chromeos::PowerManagerClient::TabletMode::ON) {
authenticated_base_id_.clear();
pairing_status_ = DetachableBasePairingStatus::kNone;
NotifyBaseRequiresFirmwareUpdate(false /*requires_update*/);
}
if (GetPairingStatus() != old_pairing_status)
NotifyPairingStatusChanged();
}
DetachableBaseHandler::DetachableBaseId
DetachableBaseHandler::GetLastUsedDeviceForUser(const UserInfo& user) const {
const auto it = last_used_devices_.find(user.account_id);
// If the last used device was set within this session, bypass local state.
if (it != last_used_devices_.end())
return it->second;
// For ephemeral lookup do not attempt getting the value from local state;
// return empty string instead.
if (user.is_ephemeral)
return "";
const base::Value::Dict& detachable_base_info =
local_state_->GetDict(prefs::kDetachableBaseDevices);
const base::Value::Dict* account_info =
detachable_base_info.FindDictByDottedPath(
GetKeyForPrefs(user.account_id));
if (!account_info)
return "";
const std::string* last_used =
account_info->FindString(kLastUsedByUserPrefKey);
return last_used ? *last_used : "";
}
void DetachableBaseHandler::NotifyPairingStatusChanged() {
for (auto& observer : observers_)
observer.OnDetachableBasePairingStatusChanged(GetPairingStatus());
}
void DetachableBaseHandler::NotifyBaseRequiresFirmwareUpdate(
bool requires_update) {
for (auto& observer : observers_)
observer.OnDetachableBaseRequiresUpdateChanged(requires_update);
}
} // namespace ash