| // 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/grandfathered_easy_unlock_host_disabler.h" |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/timer/timer.h" |
| #include "chromeos/components/multidevice/logging/logging.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "chromeos/services/device_sync/public/cpp/device_sync_client.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| |
| namespace chromeos { |
| namespace multidevice_setup { |
| |
| namespace { |
| |
| // Name of the prefs that stores the legacy device ID and Instance ID of the |
| // device which still potentially needs to have kSmartLockHost disabled on it. |
| const char kEasyUnlockHostIdToDisablePrefName[] = |
| "multidevice_setup.easy_unlock_host_id_to_disable"; |
| const char kEasyUnlockHostInstanceIdToDisablePrefName[] = |
| "multidevice_setup.easy_unlock_host_instance_id_to_disable"; |
| |
| const char kNoDevice[] = ""; |
| |
| // The number of minutes to wait before retrying a failed attempt. |
| const int kNumMinutesBetweenRetries = 5; |
| |
| bool IsEasyUnlockHost(const multidevice::RemoteDeviceRef& device) { |
| return device.GetSoftwareFeatureState( |
| multidevice::SoftwareFeature::kSmartLockHost) == |
| multidevice::SoftwareFeatureState::kEnabled; |
| } |
| |
| } // namespace |
| |
| // static |
| GrandfatheredEasyUnlockHostDisabler::Factory* |
| GrandfatheredEasyUnlockHostDisabler::Factory::test_factory_ = nullptr; |
| |
| // static |
| std::unique_ptr<GrandfatheredEasyUnlockHostDisabler> |
| GrandfatheredEasyUnlockHostDisabler::Factory::Create( |
| HostBackendDelegate* host_backend_delegate, |
| device_sync::DeviceSyncClient* device_sync_client, |
| PrefService* pref_service, |
| std::unique_ptr<base::OneShotTimer> timer) { |
| if (test_factory_) { |
| return test_factory_->CreateInstance(host_backend_delegate, |
| device_sync_client, pref_service, |
| std::move(timer)); |
| } |
| |
| return base::WrapUnique(new GrandfatheredEasyUnlockHostDisabler( |
| host_backend_delegate, device_sync_client, pref_service, |
| std::move(timer))); |
| } |
| |
| // static |
| void GrandfatheredEasyUnlockHostDisabler::Factory::SetFactoryForTesting( |
| Factory* test_factory) { |
| test_factory_ = test_factory; |
| } |
| |
| GrandfatheredEasyUnlockHostDisabler::Factory::~Factory() = default; |
| |
| // static |
| void GrandfatheredEasyUnlockHostDisabler::RegisterPrefs( |
| PrefRegistrySimple* registry) { |
| registry->RegisterStringPref(kEasyUnlockHostIdToDisablePrefName, kNoDevice); |
| registry->RegisterStringPref(kEasyUnlockHostInstanceIdToDisablePrefName, |
| kNoDevice); |
| } |
| |
| GrandfatheredEasyUnlockHostDisabler::~GrandfatheredEasyUnlockHostDisabler() { |
| host_backend_delegate_->RemoveObserver(this); |
| } |
| |
| GrandfatheredEasyUnlockHostDisabler::GrandfatheredEasyUnlockHostDisabler( |
| HostBackendDelegate* host_backend_delegate, |
| device_sync::DeviceSyncClient* device_sync_client, |
| PrefService* pref_service, |
| std::unique_ptr<base::OneShotTimer> timer) |
| : host_backend_delegate_(host_backend_delegate), |
| device_sync_client_(device_sync_client), |
| pref_service_(pref_service), |
| timer_(std::move(timer)), |
| current_better_together_host_( |
| host_backend_delegate_->GetMultiDeviceHostFromBackend()) { |
| host_backend_delegate_->AddObserver(this); |
| |
| // There might be a device stored in the pref waiting for kSmartLockHost to |
| // be disabled. |
| DisableEasyUnlockHostIfNecessary(); |
| } |
| |
| void GrandfatheredEasyUnlockHostDisabler::OnHostChangedOnBackend() { |
| // kSmartLockHost possibly needs to be disabled on the previous |
| // BetterTogether host. |
| SetPotentialEasyUnlockHostToDisable(current_better_together_host_); |
| |
| // Retrieve the new BetterTogether host. |
| current_better_together_host_ = |
| host_backend_delegate_->GetMultiDeviceHostFromBackend(); |
| |
| DisableEasyUnlockHostIfNecessary(); |
| } |
| |
| void GrandfatheredEasyUnlockHostDisabler::DisableEasyUnlockHostIfNecessary() { |
| timer_->Stop(); |
| |
| base::Optional<multidevice::RemoteDeviceRef> host_to_disable = |
| GetEasyUnlockHostToDisable(); |
| |
| if (!host_to_disable) |
| return; |
| |
| PA_LOG(VERBOSE) << "Attempting to disable kSmartLockHost on device " |
| << host_to_disable->GetTruncatedDeviceIdForLogs(); |
| if (features::ShouldUseV1DeviceSync()) { |
| // Even if the host 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(!host_to_disable->public_key().empty()); |
| device_sync_client_->SetSoftwareFeatureState( |
| host_to_disable->public_key(), |
| multidevice::SoftwareFeature::kSmartLockHost, false /* enabled */, |
| false /* is_exclusive */, |
| base::BindOnce( |
| &GrandfatheredEasyUnlockHostDisabler::OnDisableEasyUnlockHostResult, |
| weak_ptr_factory_.GetWeakPtr(), *host_to_disable)); |
| } else { |
| DCHECK(!host_to_disable->instance_id().empty()); |
| device_sync_client_->SetFeatureStatus( |
| host_to_disable->instance_id(), |
| multidevice::SoftwareFeature::kSmartLockHost, |
| device_sync::FeatureStatusChange::kDisable, |
| base::BindOnce( |
| &GrandfatheredEasyUnlockHostDisabler::OnDisableEasyUnlockHostResult, |
| weak_ptr_factory_.GetWeakPtr(), *host_to_disable)); |
| } |
| } |
| |
| void GrandfatheredEasyUnlockHostDisabler::OnDisableEasyUnlockHostResult( |
| multidevice::RemoteDeviceRef device, |
| device_sync::mojom::NetworkRequestResult result_code) { |
| bool success = |
| result_code == device_sync::mojom::NetworkRequestResult::kSuccess; |
| |
| if (success) { |
| PA_LOG(VERBOSE) << "Successfully disabled kSmartLockHost on device " |
| << device.GetTruncatedDeviceIdForLogs(); |
| } else { |
| PA_LOG(WARNING) << "Failed to disable kSmartLockHost on device " |
| << device.GetTruncatedDeviceIdForLogs() |
| << ", Error code: " << result_code; |
| } |
| |
| // Return if the EasyUnlock host to disable changed between calls to |
| // DisableEasyUnlockHostIfNecessary() and OnDisableEasyUnlockHostResult(). |
| if (device != GetEasyUnlockHostToDisable()) |
| return; |
| |
| if (success) { |
| SetPotentialEasyUnlockHostToDisable(base::nullopt); |
| return; |
| } |
| |
| PA_LOG(WARNING) << "Retrying in " << kNumMinutesBetweenRetries |
| << " minutes if necessary."; |
| timer_->Start(FROM_HERE, |
| base::TimeDelta::FromMinutes(kNumMinutesBetweenRetries), |
| base::BindOnce(&GrandfatheredEasyUnlockHostDisabler:: |
| DisableEasyUnlockHostIfNecessary, |
| base::Unretained(this))); |
| } |
| |
| void GrandfatheredEasyUnlockHostDisabler::SetPotentialEasyUnlockHostToDisable( |
| base::Optional<multidevice::RemoteDeviceRef> device) { |
| pref_service_->SetString(kEasyUnlockHostIdToDisablePrefName, |
| !device || device->GetDeviceId().empty() |
| ? kNoDevice |
| : device->GetDeviceId()); |
| pref_service_->SetString(kEasyUnlockHostInstanceIdToDisablePrefName, |
| !device || device->instance_id().empty() |
| ? kNoDevice |
| : device->instance_id()); |
| } |
| |
| base::Optional<multidevice::RemoteDeviceRef> |
| GrandfatheredEasyUnlockHostDisabler::GetEasyUnlockHostToDisable() { |
| std::string legacy_device_id = |
| pref_service_->GetString(kEasyUnlockHostIdToDisablePrefName); |
| std::string instance_id = |
| pref_service_->GetString(kEasyUnlockHostInstanceIdToDisablePrefName); |
| if (legacy_device_id == kNoDevice && instance_id == kNoDevice) |
| return base::nullopt; |
| |
| multidevice::RemoteDeviceRefList synced_devices = |
| device_sync_client_->GetSyncedDevices(); |
| auto it = std::find_if( |
| synced_devices.begin(), synced_devices.end(), |
| [&legacy_device_id, &instance_id](const auto& remote_device) { |
| return (legacy_device_id != kNoDevice && |
| remote_device.GetDeviceId() == legacy_device_id) || |
| (instance_id != kNoDevice && |
| remote_device.instance_id() == instance_id); |
| }); |
| |
| // The device does not need to have kSmartLockHost disabled if any of the |
| // following are true: |
| // - the device is not in the list of synced devices anymore, |
| // - the device is not the current EasyUnlock host, or |
| // - the device is the BetterTogether host. |
| if (it == synced_devices.end() || !IsEasyUnlockHost(*it) || |
| *it == current_better_together_host_) { |
| SetPotentialEasyUnlockHostToDisable(base::nullopt); |
| return base::nullopt; |
| } |
| |
| return *it; |
| } |
| |
| } // namespace multidevice_setup |
| |
| } // namespace chromeos |