123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- // 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 "ash/services/multidevice_setup/host_backend_delegate_impl.h"
- #include <algorithm>
- #include <sstream>
- #include "ash/components/multidevice/logging/logging.h"
- #include "ash/components/multidevice/software_feature.h"
- #include "ash/components/multidevice/software_feature_state.h"
- #include "ash/constants/ash_features.h"
- #include "ash/services/device_sync/feature_status_change.h"
- #include "ash/services/multidevice_setup/eligible_host_devices_provider.h"
- #include "base/bind.h"
- #include "base/containers/contains.h"
- #include "base/memory/ptr_util.h"
- #include "components/prefs/pref_registry_simple.h"
- #include "components/prefs/pref_service.h"
- namespace ash {
- namespace multidevice_setup {
- namespace {
- // Name of the pref which stores the ID of the host which is pending being set
- // on the back-end.
- const char kPendingRequestHostIdPrefName[] =
- "multidevice_setup.pending_request_host_id";
- // String to use for the pending host ID preference entry when the pending
- // request is to remove the current host.
- const char kPendingRemovalOfCurrentHost[] = "pendingRemovalOfCurrentHost";
- // String to use for the pending host ID when there is no pending request.
- const char kNoPendingRequest[] = "";
- // The number of minutes to wait before retrying a failed attempt.
- const int kNumMinutesBetweenRetries = 5;
- const char kNoHostForLogging[] = "[no host]";
- } // namespace
- // static
- HostBackendDelegateImpl::Factory*
- HostBackendDelegateImpl::Factory::test_factory_ = nullptr;
- // static
- std::unique_ptr<HostBackendDelegate> HostBackendDelegateImpl::Factory::Create(
- EligibleHostDevicesProvider* eligible_host_devices_provider,
- PrefService* pref_service,
- device_sync::DeviceSyncClient* device_sync_client,
- std::unique_ptr<base::OneShotTimer> timer) {
- if (test_factory_) {
- return test_factory_->CreateInstance(eligible_host_devices_provider,
- pref_service, device_sync_client,
- std::move(timer));
- }
- return base::WrapUnique(
- new HostBackendDelegateImpl(eligible_host_devices_provider, pref_service,
- device_sync_client, std::move(timer)));
- }
- // static
- void HostBackendDelegateImpl::Factory::SetFactoryForTesting(
- Factory* test_factory) {
- test_factory_ = test_factory;
- }
- HostBackendDelegateImpl::Factory::~Factory() = default;
- // static
- void HostBackendDelegateImpl::RegisterPrefs(PrefRegistrySimple* registry) {
- registry->RegisterStringPref(kPendingRequestHostIdPrefName,
- kNoPendingRequest);
- }
- HostBackendDelegateImpl::HostBackendDelegateImpl(
- EligibleHostDevicesProvider* eligible_host_devices_provider,
- PrefService* pref_service,
- device_sync::DeviceSyncClient* device_sync_client,
- std::unique_ptr<base::OneShotTimer> timer)
- : HostBackendDelegate(),
- eligible_host_devices_provider_(eligible_host_devices_provider),
- pref_service_(pref_service),
- device_sync_client_(device_sync_client),
- timer_(std::move(timer)) {
- device_sync_client_->AddObserver(this);
- host_from_last_sync_ = GetHostFromDeviceSync();
- if (HasPendingHostRequest())
- AttemptNetworkRequest(false /* is_retry */);
- }
- HostBackendDelegateImpl::~HostBackendDelegateImpl() {
- device_sync_client_->RemoveObserver(this);
- }
- void HostBackendDelegateImpl::AttemptToSetMultiDeviceHostOnBackend(
- const absl::optional<multidevice::RemoteDeviceRef>& host_device) {
- if (host_device && !IsHostEligible(*host_device)) {
- PA_LOG(WARNING) << "HostBackendDelegateImpl::"
- << "AttemptToSetMultiDeviceHostOnBackend(): Tried to set a "
- << "device as host, but that device is not an eligible "
- << "host: " << host_device->GetInstanceIdDeviceIdForLogs();
- return;
- }
- // If the device on the back-end is already |host_device|, there no longer
- // needs to be a pending request.
- if (host_from_last_sync_ == host_device) {
- SetPendingHostRequest(kNoPendingRequest);
- return;
- }
- // Stop the timer, since a new attempt is being started.
- timer_->Stop();
- if (host_device) {
- if (features::ShouldUseV1DeviceSync()) {
- SetPendingHostRequest(host_device->GetDeviceId());
- } else {
- DCHECK(!host_device->instance_id().empty());
- SetPendingHostRequest(host_device->instance_id());
- }
- } else {
- SetPendingHostRequest(kPendingRemovalOfCurrentHost);
- }
- AttemptNetworkRequest(false /* is_retry */);
- }
- bool HostBackendDelegateImpl::HasPendingHostRequest() {
- const std::string pending_host_id_from_prefs =
- pref_service_->GetString(kPendingRequestHostIdPrefName);
- if (pending_host_id_from_prefs == kNoPendingRequest)
- return false;
- if (pending_host_id_from_prefs == kPendingRemovalOfCurrentHost) {
- // If the pending request is to remove the current host but there is no
- // current host, the host was removed by another device while this device
- // was offline.
- if (!host_from_last_sync_) {
- SetPendingHostRequest(kNoPendingRequest);
- return false;
- }
- // Otherwise, there still is a pending request to remove the current host.
- return true;
- }
- // By this point, |pending_host_id_from_prefs| refers to a real device ID and
- // not one of the two sentinel values.
- if (FindDeviceById(pending_host_id_from_prefs))
- return true;
- // If a request was pending for a specific host device, but that device is no
- // longer present on the user's account, there is no longer a pending request.
- // TODO(https://crbug.com/936273): Track frequency of unrecognized host IDs.
- // If the following scenarios occur before the pending host request completes,
- // the persisted host ID will not be recognized, and the user will need to go
- // through setup again:
- // * The device was actually removed from the user's account.
- // * A public key is persisted, v1 DeviceSync is disabled, and the v2 device
- // data hasn't been decrypted.
- // * Instance ID is persisted, v1 DeviceSync in re-enabled and the Instance
- // ID cannot be found in the device list.
- // We expect all of these scenarios to be very rare.
- SetPendingHostRequest(kNoPendingRequest);
- return false;
- }
- absl::optional<multidevice::RemoteDeviceRef>
- HostBackendDelegateImpl::GetPendingHostRequest() const {
- const std::string pending_host_id_from_prefs =
- pref_service_->GetString(kPendingRequestHostIdPrefName);
- DCHECK_NE(pending_host_id_from_prefs, kNoPendingRequest)
- << "HostBackendDelegateImpl::GetPendingHostRequest(): Tried "
- << "to get pending host request, but there was no pending "
- << "host request.";
- if (pending_host_id_from_prefs == kPendingRemovalOfCurrentHost)
- return absl::nullopt;
- absl::optional<multidevice::RemoteDeviceRef> pending_host =
- FindDeviceById(pending_host_id_from_prefs);
- DCHECK(pending_host)
- << "HostBackendDelegateImpl::GetPendingHostRequest(): Tried to get "
- << "pending host request, but the pending host ID was not present.";
- return pending_host;
- }
- absl::optional<multidevice::RemoteDeviceRef>
- HostBackendDelegateImpl::GetMultiDeviceHostFromBackend() const {
- return host_from_last_sync_;
- }
- bool HostBackendDelegateImpl::IsHostEligible(
- const multidevice::RemoteDeviceRef& provided_host) {
- return base::Contains(
- eligible_host_devices_provider_->GetEligibleHostDevices(), provided_host);
- }
- void HostBackendDelegateImpl::SetPendingHostRequest(
- const std::string& pending_host_id) {
- const std::string host_id_from_prefs_before_call =
- pref_service_->GetString(kPendingRequestHostIdPrefName);
- if (pending_host_id == host_id_from_prefs_before_call)
- return;
- pref_service_->SetString(kPendingRequestHostIdPrefName, pending_host_id);
- timer_->Stop();
- NotifyPendingHostRequestChange();
- }
- absl::optional<multidevice::RemoteDeviceRef>
- HostBackendDelegateImpl::FindDeviceById(const std::string& id) const {
- DCHECK(!id.empty());
- for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) {
- if (features::ShouldUseV1DeviceSync()) {
- if (id == remote_device.GetDeviceId())
- return remote_device;
- } else {
- if (id == remote_device.instance_id())
- return remote_device;
- }
- }
- return absl::nullopt;
- }
- void HostBackendDelegateImpl::AttemptNetworkRequest(bool is_retry) {
- DCHECK(HasPendingHostRequest())
- << "HostBackendDelegateImpl::AttemptNetworkRequest(): Tried to attempt a "
- << "network request, but there was no pending host request.";
- absl::optional<multidevice::RemoteDeviceRef> pending_host_request =
- GetPendingHostRequest();
- // If |pending_host_request| is non-null, the request should be to set that
- // device. If it is null, the pending request is to remove the current host.
- multidevice::RemoteDeviceRef device_to_set =
- pending_host_request ? *pending_host_request : *host_from_last_sync_;
- // Likewise, if |pending_host_request| is non-null, that device should be
- // enabled, and if it is null, the old device should be disabled.
- bool should_enable = pending_host_request != absl::nullopt;
- PA_LOG(INFO) << "HostBackendDelegateImpl::AttemptNetworkRequest(): "
- << (is_retry ? "Retrying attempt" : "Attempting") << " to "
- << (should_enable ? "enable" : "disable")
- << " the host: " << device_to_set.GetInstanceIdDeviceIdForLogs();
- if (features::ShouldUseV1DeviceSync()) {
- // Even if the |device_to_set| has a non-trivial Instance ID, we still
- // invoke the v1 DeviceSync RPC to set the feature state. This ensures that
- // GmsCore will be notified of the change regardless of what version of
- // DeviceSync it is running. The v1 and v2 RPCs to change feature states
- // ultimately update the same backend database entry. Note: The
- // RemoteDeviceProvider guarantees that every device will have a public key
- // while v1 DeviceSync is enabled.
- DCHECK(!device_to_set.public_key().empty());
- device_sync_client_->SetSoftwareFeatureState(
- device_to_set.public_key(),
- multidevice::SoftwareFeature::kBetterTogetherHost,
- should_enable /* enabled */, should_enable /* is_exclusive */,
- base::BindOnce(
- &HostBackendDelegateImpl::OnSetHostNetworkRequestFinished,
- weak_ptr_factory_.GetWeakPtr(), device_to_set, should_enable));
- } else {
- DCHECK(!device_to_set.instance_id().empty());
- device_sync_client_->SetFeatureStatus(
- device_to_set.instance_id(),
- multidevice::SoftwareFeature::kBetterTogetherHost,
- should_enable ? device_sync::FeatureStatusChange::kEnableExclusively
- : device_sync::FeatureStatusChange::kDisable,
- base::BindOnce(
- &HostBackendDelegateImpl::OnSetHostNetworkRequestFinished,
- weak_ptr_factory_.GetWeakPtr(), device_to_set, should_enable));
- }
- }
- void HostBackendDelegateImpl::OnNewDevicesSynced() {
- absl::optional<multidevice::RemoteDeviceRef> host_from_sync =
- GetHostFromDeviceSync();
- if (host_from_last_sync_ == host_from_sync)
- return;
- PA_LOG(INFO) << "HostBackendDelegateImpl::OnNewDevicesSynced(): New host "
- << "device has been set. Old host: "
- << (host_from_last_sync_
- ? host_from_last_sync_->GetInstanceIdDeviceIdForLogs()
- : kNoHostForLogging)
- << ", New host: "
- << (host_from_sync
- ? host_from_sync->GetInstanceIdDeviceIdForLogs()
- : kNoHostForLogging);
- host_from_last_sync_ = host_from_sync;
- // If there is a pending request and the new host fulfills that pending
- // request, there is no longer a pending request.
- if (HasPendingHostRequest() &&
- host_from_last_sync_ == GetPendingHostRequest()) {
- SetPendingHostRequest(kNoPendingRequest);
- }
- NotifyHostChangedOnBackend();
- }
- absl::optional<multidevice::RemoteDeviceRef>
- HostBackendDelegateImpl::GetHostFromDeviceSync() {
- multidevice::RemoteDeviceRefList synced_devices =
- device_sync_client_->GetSyncedDevices();
- auto it = std::find_if(
- synced_devices.begin(), synced_devices.end(),
- [](const auto& remote_device) {
- multidevice::SoftwareFeatureState host_state =
- remote_device.GetSoftwareFeatureState(
- multidevice::SoftwareFeature::kBetterTogetherHost);
- return host_state == multidevice::SoftwareFeatureState::kEnabled;
- });
- if (it == synced_devices.end())
- return absl::nullopt;
- return *it;
- }
- void HostBackendDelegateImpl::OnSetHostNetworkRequestFinished(
- multidevice::RemoteDeviceRef device_for_request,
- bool attempted_to_enable,
- device_sync::mojom::NetworkRequestResult result_code) {
- bool success =
- result_code == device_sync::mojom::NetworkRequestResult::kSuccess;
- std::stringstream ss;
- ss << "HostBackendDelegateImpl::OnSetHostNetworkRequestFinished(): "
- << (success ? "Completed successful" : "Failure requesting") << " "
- << "host change for " << device_for_request.GetInstanceIdDeviceIdForLogs()
- << ". Attempted to enable: " << (attempted_to_enable ? "true" : "false");
- if (success) {
- PA_LOG(VERBOSE) << ss.str();
- return;
- }
- ss << ", Error code: " << result_code;
- PA_LOG(WARNING) << ss.str();
- if (!HasPendingHostRequest())
- return;
- absl::optional<multidevice::RemoteDeviceRef> pending_host_request =
- GetPendingHostRequest();
- bool failed_request_was_to_set_pending_host =
- attempted_to_enable && pending_host_request &&
- *pending_host_request == device_for_request;
- bool failed_request_was_to_remove_pending_host =
- !attempted_to_enable && !pending_host_request &&
- device_for_request == host_from_last_sync_;
- // If the request which failed corresponds to the most recent call to
- // AttemptToSetMultiDeviceHostOnBackend(), alert observers that this request
- // failed and schedule a retry.
- if (failed_request_was_to_set_pending_host ||
- failed_request_was_to_remove_pending_host) {
- NotifyBackendRequestFailed();
- timer_->Start(
- FROM_HERE, base::Minutes(kNumMinutesBetweenRetries),
- base::BindOnce(&HostBackendDelegateImpl::AttemptNetworkRequest,
- base::Unretained(this), true /* is_retry */));
- }
- }
- } // namespace multidevice_setup
- } // namespace ash
|