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