blob: 78c574ad0e9bfeb7822820bdeeb8cd1b76fa381e [file] [log] [blame]
// Copyright 2018 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 "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
#include <set>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/time/clock.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/services/multidevice_setup/host_device_timestamp_manager.h"
#include "chromeos/services/multidevice_setup/host_status_provider_impl.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace chromeos {
namespace multidevice_setup {
namespace {
const int64_t kTimestampNotSet = 0;
const char kNoHost[] = "";
} // namespace
// static
AccountStatusChangeDelegateNotifierImpl::Factory*
AccountStatusChangeDelegateNotifierImpl::Factory::test_factory_ = nullptr;
// static
AccountStatusChangeDelegateNotifierImpl::Factory*
AccountStatusChangeDelegateNotifierImpl::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<Factory> factory;
return factory.get();
}
// static
void AccountStatusChangeDelegateNotifierImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
AccountStatusChangeDelegateNotifierImpl::Factory::~Factory() = default;
std::unique_ptr<AccountStatusChangeDelegateNotifier>
AccountStatusChangeDelegateNotifierImpl::Factory::BuildInstance(
HostStatusProvider* host_status_provider,
PrefService* pref_service,
HostDeviceTimestampManager* host_device_timestamp_manager,
OobeCompletionTracker* oobe_completion_tracker,
base::Clock* clock) {
return base::WrapUnique(new AccountStatusChangeDelegateNotifierImpl(
host_status_provider, pref_service, host_device_timestamp_manager,
oobe_completion_tracker, clock));
}
// static
void AccountStatusChangeDelegateNotifierImpl::RegisterPrefs(
PrefRegistrySimple* registry) {
// Records the timestamps (in milliseconds since UNIX Epoch, aka JavaTime) of
// the last instance the delegate was notified for each of the changes listed
// in the class description.
registry->RegisterInt64Pref(kNewUserPotentialHostExistsPrefName,
kTimestampNotSet);
registry->RegisterInt64Pref(kExistingUserHostSwitchedPrefName,
kTimestampNotSet);
registry->RegisterInt64Pref(kExistingUserChromebookAddedPrefName,
kTimestampNotSet);
registry->RegisterInt64Pref(kOobeSetupFlowTimestampPrefName,
kTimestampNotSet);
registry->RegisterStringPref(
kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName, kNoHost);
}
AccountStatusChangeDelegateNotifierImpl::
~AccountStatusChangeDelegateNotifierImpl() {
host_status_provider_->RemoveObserver(this);
oobe_completion_tracker_->RemoveObserver(this);
}
void AccountStatusChangeDelegateNotifierImpl::OnDelegateSet() {
CheckForMultiDeviceEvents(host_status_provider_->GetHostWithStatus());
}
// static
const char AccountStatusChangeDelegateNotifierImpl::
kNewUserPotentialHostExistsPrefName[] =
"multidevice_setup.new_user_potential_host_exists";
// static
const char AccountStatusChangeDelegateNotifierImpl::
kExistingUserHostSwitchedPrefName[] =
"multidevice_setup.existing_user_host_switched";
// static
const char AccountStatusChangeDelegateNotifierImpl::
kExistingUserChromebookAddedPrefName[] =
"multidevice_setup.existing_user_chromebook_added";
// Note that, despite the pref string name, this pref only records the IDs of
// verified hosts. In particular, if a host has been set but is waiting for
// verification, it will not recorded.
// static
const char AccountStatusChangeDelegateNotifierImpl::
kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName[] =
"multidevice_setup.host_device_id_from_most_recent_sync";
// The timestamps (in milliseconds since UNIX Epoch, aka JavaTime) of the user
// seeing setup flow in OOBE. If it is 0, the user did not see the setup flow in
// OOBE.
// static
const char
AccountStatusChangeDelegateNotifierImpl::kOobeSetupFlowTimestampPrefName[] =
"multidevice_setup.oobe_setup_flow_timestamp ";
AccountStatusChangeDelegateNotifierImpl::
AccountStatusChangeDelegateNotifierImpl(
HostStatusProvider* host_status_provider,
PrefService* pref_service,
HostDeviceTimestampManager* host_device_timestamp_manager,
OobeCompletionTracker* oobe_completion_tracker,
base::Clock* clock)
: host_status_provider_(host_status_provider),
pref_service_(pref_service),
host_device_timestamp_manager_(host_device_timestamp_manager),
oobe_completion_tracker_(oobe_completion_tracker),
clock_(clock) {
verified_host_device_id_from_most_recent_update_ =
LoadHostDeviceIdFromEndOfPreviousSession();
host_status_provider_->AddObserver(this);
oobe_completion_tracker_->AddObserver(this);
}
void AccountStatusChangeDelegateNotifierImpl::OnHostStatusChange(
const HostStatusProvider::HostStatusWithDevice& host_status_with_device) {
CheckForMultiDeviceEvents(host_status_with_device);
}
void AccountStatusChangeDelegateNotifierImpl::OnOobeCompleted() {
pref_service_->SetInt64(kOobeSetupFlowTimestampPrefName,
clock_->Now().ToJavaTime());
if (delegate())
delegate()->OnNoLongerNewUser();
}
void AccountStatusChangeDelegateNotifierImpl::CheckForMultiDeviceEvents(
const HostStatusProvider::HostStatusWithDevice& host_status_with_device) {
if (!delegate()) {
PA_LOG(WARNING)
<< "AccountStatusChangeDelegateNotifierImpl::"
<< "CheckForMultiDeviceEvents(): Tried to check for potential "
<< "events, but no delegate was set.";
return;
}
// Track and update host status.
base::Optional<mojom::HostStatus> host_status_before_update =
host_status_from_most_recent_update_;
host_status_from_most_recent_update_ = host_status_with_device.host_status();
// Track and update verified host info.
base::Optional<std::string> verified_host_device_id_before_update =
verified_host_device_id_from_most_recent_update_;
// Check if a host has been verified.
if (host_status_with_device.host_status() ==
mojom::HostStatus::kHostVerified) {
verified_host_device_id_from_most_recent_update_ =
host_status_with_device.host_device()->GetDeviceId();
pref_service_->SetString(
kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName,
*verified_host_device_id_from_most_recent_update_);
} else {
// No host set.
verified_host_device_id_from_most_recent_update_.reset();
pref_service_->SetString(
kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName, kNoHost);
}
CheckForNewUserPotentialHostExistsEvent(host_status_with_device);
CheckForNoLongerNewUserEvent(host_status_with_device,
host_status_before_update);
CheckForExistingUserHostSwitchedEvent(host_status_with_device,
verified_host_device_id_before_update);
CheckForExistingUserChromebookAddedEvent(
host_status_with_device, verified_host_device_id_before_update);
}
void AccountStatusChangeDelegateNotifierImpl::
CheckForNewUserPotentialHostExistsEvent(
const HostStatusProvider::HostStatusWithDevice&
host_status_with_device) {
// We do not notify the user if they already had a chance to go through setup
// flow in OOBE.
if (pref_service_->GetInt64(kOobeSetupFlowTimestampPrefName) !=
kTimestampNotSet) {
return;
}
// We only check for new user events if there is no enabled host.
if (verified_host_device_id_from_most_recent_update_)
return;
// If the observer has been notified of a potential verified host in the past,
// they are not considered a new user.
if (pref_service_->GetInt64(kNewUserPotentialHostExistsPrefName) !=
kTimestampNotSet ||
pref_service_->GetInt64(kExistingUserChromebookAddedPrefName) !=
kTimestampNotSet) {
return;
}
// kEligibleHostExistsButNoHostSet is the only HostStatus that can describe
// a new user.
if (host_status_with_device.host_status() !=
mojom::HostStatus::kEligibleHostExistsButNoHostSet) {
return;
}
delegate()->OnPotentialHostExistsForNewUser();
pref_service_->SetInt64(kNewUserPotentialHostExistsPrefName,
clock_->Now().ToJavaTime());
}
void AccountStatusChangeDelegateNotifierImpl::CheckForNoLongerNewUserEvent(
const HostStatusProvider::HostStatusWithDevice& host_status_with_device,
const base::Optional<mojom::HostStatus> host_status_before_update) {
// We are only looking for the case when the host status switched from
// kEligibleHostExistsButNoHostSet to something else.
if (host_status_with_device.host_status() ==
mojom::HostStatus::kEligibleHostExistsButNoHostSet ||
host_status_before_update !=
mojom::HostStatus::kEligibleHostExistsButNoHostSet) {
return;
}
// If the user has ever had a verified host, they have already left the 'new
// user' state.
if (pref_service_->GetInt64(kExistingUserChromebookAddedPrefName) !=
kTimestampNotSet) {
return;
}
delegate()->OnNoLongerNewUser();
}
void AccountStatusChangeDelegateNotifierImpl::
CheckForExistingUserHostSwitchedEvent(
const HostStatusProvider::HostStatusWithDevice& host_status_with_device,
const base::Optional<std::string>&
verified_host_device_id_before_update) {
// The host switched event requires both a pre-update and a post-update
// verified host.
if (!verified_host_device_id_from_most_recent_update_ ||
!verified_host_device_id_before_update) {
return;
}
// If the host stayed the same, there was no switch.
if (*verified_host_device_id_from_most_recent_update_ ==
*verified_host_device_id_before_update) {
return;
}
delegate()->OnConnectedHostSwitchedForExistingUser(
host_status_with_device.host_device()->name());
pref_service_->SetInt64(kExistingUserHostSwitchedPrefName,
clock_->Now().ToJavaTime());
}
void AccountStatusChangeDelegateNotifierImpl::
CheckForExistingUserChromebookAddedEvent(
const HostStatusProvider::HostStatusWithDevice& host_status_with_device,
const base::Optional<std::string>&
verified_host_device_id_before_update) {
// The Chromebook added event requires that a verified host was found by the
// update, i.e. there was no verified host before the host status update but
// afterward there was a verified host.
if (!verified_host_device_id_from_most_recent_update_ ||
verified_host_device_id_before_update) {
return;
}
// This event is specific to setup taking place on a different Chromebook.
if (host_device_timestamp_manager_->WasHostSetFromThisChromebook())
return;
delegate()->OnNewChromebookAddedForExistingUser(
host_status_with_device.host_device()->name());
pref_service_->SetInt64(kExistingUserChromebookAddedPrefName,
clock_->Now().ToJavaTime());
}
base::Optional<std::string> AccountStatusChangeDelegateNotifierImpl::
LoadHostDeviceIdFromEndOfPreviousSession() {
std::string verified_host_device_id_from_most_recent_update =
pref_service_->GetString(
kVerifiedHostDeviceIdFromMostRecentHostStatusUpdatePrefName);
if (verified_host_device_id_from_most_recent_update.empty())
return base::nullopt;
return verified_host_device_id_from_most_recent_update;
}
} // namespace multidevice_setup
} // namespace chromeos