blob: 147088c4b31b2f3be129a8c2444e81297fc3484f [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/host_verifier_impl.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/components/multidevice/software_feature.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace chromeos {
namespace multidevice_setup {
namespace {
// Software features which, when enabled, represent a verified host.
constexpr const multidevice::SoftwareFeature kPotentialHostFeatures[] = {
multidevice::SoftwareFeature::kSmartLockHost,
multidevice::SoftwareFeature::kInstantTetheringHost,
multidevice::SoftwareFeature::kMessagesForWebHost};
// Name of the preference containing the time (in milliseconds since Unix
// epoch) at which a verification attempt should be retried. If the preference
// value is kTimestampNotSet, no retry is scheduled.
const char kRetryTimestampPrefName[] =
"multidevice_setup.current_retry_timestamp_ms";
// Value set for the kRetryTimestampPrefName preference when no retry attempt is
// underway (i.e., verification is complete or there is no current host).
const int64_t kTimestampNotSet = 0;
// Name of the preference containing the time delta (in ms) between the
// timestamp present in the kRetryTimestampPrefName preference and the attempt
// before that one. If the value of kRetryTimestampPrefName is kTimestampNotSet,
// the value at this preference is meaningless.
const char kLastUsedTimeDeltaMsPrefName[] =
"multidevice_setup.last_used_time_delta_ms";
// Delta to set for the first retry.
constexpr const base::TimeDelta kFirstRetryDelta =
base::TimeDelta::FromMinutes(10);
// The multiplier for increasing the backoff timer between retries.
const double kExponentialBackoffMultiplier = 1.5;
} // namespace
// static
HostVerifierImpl::Factory* HostVerifierImpl::Factory::test_factory_ = nullptr;
// static
HostVerifierImpl::Factory* HostVerifierImpl::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<Factory> factory;
return factory.get();
}
// static
void HostVerifierImpl::Factory::SetFactoryForTesting(Factory* test_factory) {
test_factory_ = test_factory;
}
HostVerifierImpl::Factory::~Factory() = default;
std::unique_ptr<HostVerifier> HostVerifierImpl::Factory::BuildInstance(
HostBackendDelegate* host_backend_delegate,
device_sync::DeviceSyncClient* device_sync_client,
PrefService* pref_service,
base::Clock* clock,
std::unique_ptr<base::OneShotTimer> timer) {
return base::WrapUnique(new HostVerifierImpl(host_backend_delegate,
device_sync_client, pref_service,
clock, std::move(timer)));
}
// static
void HostVerifierImpl::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterInt64Pref(kRetryTimestampPrefName, kTimestampNotSet);
registry->RegisterInt64Pref(kLastUsedTimeDeltaMsPrefName, 0);
}
HostVerifierImpl::HostVerifierImpl(
HostBackendDelegate* host_backend_delegate,
device_sync::DeviceSyncClient* device_sync_client,
PrefService* pref_service,
base::Clock* clock,
std::unique_ptr<base::OneShotTimer> timer)
: host_backend_delegate_(host_backend_delegate),
device_sync_client_(device_sync_client),
pref_service_(pref_service),
clock_(clock),
timer_(std::move(timer)) {
host_backend_delegate_->AddObserver(this);
device_sync_client_->AddObserver(this);
UpdateRetryState();
}
HostVerifierImpl::~HostVerifierImpl() {
host_backend_delegate_->RemoveObserver(this);
device_sync_client_->RemoveObserver(this);
}
bool HostVerifierImpl::IsHostVerified() {
base::Optional<multidevice::RemoteDeviceRef> current_host =
host_backend_delegate_->GetMultiDeviceHostFromBackend();
if (!current_host)
return false;
// If a host exists on the back-end but there is a pending request to remove
// that host, the device pending removal is no longer considered verified.
if (host_backend_delegate_->HasPendingHostRequest() &&
!host_backend_delegate_->GetPendingHostRequest()) {
return false;
}
// If one or more potential host sofware features is enabled, the host is
// considered verified.
for (const auto& software_feature : kPotentialHostFeatures) {
if (current_host->GetSoftwareFeatureState(software_feature) ==
multidevice::SoftwareFeatureState::kEnabled) {
return true;
}
}
return false;
}
void HostVerifierImpl::PerformAttemptVerificationNow() {
AttemptHostVerification();
}
void HostVerifierImpl::OnHostChangedOnBackend() {
UpdateRetryState();
}
void HostVerifierImpl::OnNewDevicesSynced() {
UpdateRetryState();
}
void HostVerifierImpl::UpdateRetryState() {
// If there is no host, verification is not applicable.
if (!host_backend_delegate_->GetMultiDeviceHostFromBackend()) {
StopTimerAndClearPrefs();
return;
}
// If there is a host and it is verified, verification is no longer necessary.
if (IsHostVerified()) {
bool was_timer_running = timer_->IsRunning();
StopTimerAndClearPrefs();
if (was_timer_running)
NotifyHostVerified();
return;
}
// If |timer_| is running, an ongoing retry attempt is in progress.
if (timer_->IsRunning())
return;
int64_t timestamp_from_prefs =
pref_service_->GetInt64(kRetryTimestampPrefName);
// If no retry timer was set, set the timer to the initial value and attempt
// to verify now.
if (timestamp_from_prefs == kTimestampNotSet) {
AttemptVerificationWithInitialTimeout();
return;
}
base::Time retry_time_from_prefs =
base::Time::FromJavaTime(timestamp_from_prefs);
// If a timeout value was set but has not yet occurred, start the timer.
if (clock_->Now() < retry_time_from_prefs) {
StartTimer(retry_time_from_prefs);
return;
}
AttemptVerificationAfterInitialTimeout(retry_time_from_prefs);
}
void HostVerifierImpl::StopTimerAndClearPrefs() {
timer_->Stop();
pref_service_->SetInt64(kRetryTimestampPrefName, kTimestampNotSet);
pref_service_->SetInt64(kLastUsedTimeDeltaMsPrefName, 0);
}
void HostVerifierImpl::AttemptVerificationWithInitialTimeout() {
base::Time retry_time = clock_->Now() + kFirstRetryDelta;
pref_service_->SetInt64(kRetryTimestampPrefName, retry_time.ToJavaTime());
pref_service_->SetInt64(kLastUsedTimeDeltaMsPrefName,
kFirstRetryDelta.InMilliseconds());
StartTimer(retry_time);
AttemptHostVerification();
}
void HostVerifierImpl::AttemptVerificationAfterInitialTimeout(
const base::Time& retry_time_from_prefs) {
int64_t time_delta_ms = pref_service_->GetInt64(kLastUsedTimeDeltaMsPrefName);
DCHECK(time_delta_ms > 0);
base::Time retry_time = retry_time_from_prefs;
while (clock_->Now() >= retry_time) {
time_delta_ms *= kExponentialBackoffMultiplier;
retry_time += base::TimeDelta::FromMilliseconds(time_delta_ms);
}
pref_service_->SetInt64(kRetryTimestampPrefName, retry_time.ToJavaTime());
pref_service_->SetInt64(kLastUsedTimeDeltaMsPrefName, time_delta_ms);
StartTimer(retry_time);
AttemptHostVerification();
}
void HostVerifierImpl::StartTimer(const base::Time& time_to_fire) {
base::Time now = clock_->Now();
DCHECK(now < time_to_fire);
timer_->Start(
FROM_HERE, time_to_fire - now /* delay */,
base::Bind(&HostVerifierImpl::UpdateRetryState, base::Unretained(this)));
}
void HostVerifierImpl::AttemptHostVerification() {
base::Optional<multidevice::RemoteDeviceRef> current_host =
host_backend_delegate_->GetMultiDeviceHostFromBackend();
if (!current_host) {
PA_LOG(WARNING) << "HostVerifierImpl::AttemptHostVerification(): Cannot "
<< "attempt verification because there is no active host.";
return;
}
PA_LOG(VERBOSE) << "HostVerifierImpl::AttemptHostVerification(): Attempting "
<< "host verification now.";
device_sync_client_->FindEligibleDevices(
multidevice::SoftwareFeature::kBetterTogetherHost, base::DoNothing());
}
} // namespace multidevice_setup
} // namespace chromeos