blob: 051a558a0c9d7002bdb539acb96e72ff04471477 [file] [log] [blame]
// Copyright 2014 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/timezone/timezone_resolver.h"
#include <math.h>
#include <algorithm>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/rand_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chromeos/geolocation/geoposition.h"
#include "chromeos/geolocation/simple_geolocation_provider.h"
#include "chromeos/timezone/timezone_provider.h"
namespace chromeos {
namespace {
class TZRequest;
// Total timezone resolving process timeout.
const unsigned int kRefreshTimeZoneTimeoutSeconds = 60;
// Initial delay (for the first request).
const double kInitialRefreshIntervalSec = 3.0;
// Timezone refresh happens at least once each this interval.
const double kMaximumRefreshIntervalSec = 6.0 * 3600; // 6 hours
// Delay between refresh attempts depends on current number of requests and
// this constant.
// [interval = kInitialRefreshIntervalMS * (2 ^
// (kRefreshIntervalRequestsCountMultiplier * requests_count))]
// in seconds.
// request_number interval (seconds)
// 1 3 (initial, requests_count = 0)
// 2 24 (requests_count = 1)
// 3 1536 (requests_count = 2)
// 4 12288 (requests_count = 3)
// 5+ 21600 (maximum)
const unsigned int kRefreshIntervalRequestsCountMultiplier = 3;
// We should limit request rate on browser start to prevent server overload
// on permanent browser crash.
// If too little time has passed since previous request, initialize
// |requests_count_| with |kRefreshTimeZoneInitialRequestCountOnRateLimit|.
const double kRefreshTimeZoneMinimumDelayOnRestartSec =
10 * 60.0; // 10 minutes
const unsigned int kRefreshTimeZoneInitialRequestCountOnRateLimit = 2;
int MaxRequestsCountForInterval(const double interval_seconds) {
return log2(interval_seconds / kInitialRefreshIntervalSec) /
kRefreshIntervalRequestsCountMultiplier;
}
int IntervalForNextRequest(const int requests) {
const base::TimeDelta initial_interval =
base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec);
return static_cast<int>(initial_interval.InSecondsF() *
(2 << (static_cast<unsigned>(requests) *
kRefreshIntervalRequestsCountMultiplier)));
}
} // anonymous namespace
const char TimeZoneResolver::kLastTimeZoneRefreshTime[] =
"timezone_resolver.last_update_time";
// This class periodically refreshes location and timezone.
// It should be destroyed to stop refresh.
class TimeZoneResolver::TimeZoneResolverImpl : public base::PowerObserver {
public:
explicit TimeZoneResolverImpl(const TimeZoneResolver* resolver);
~TimeZoneResolverImpl() override;
// This is called once after the object is created.
void Start();
// PowerObserver implementation.
void OnResume() override;
// (Re)Starts timer.
void ScheduleRequest();
// Creates new TZRequest.
void CreateNewRequest();
// Called by TZRequest.
SimpleGeolocationProvider* geolocation_provider() {
return &geolocation_provider_;
}
TimeZoneProvider* timezone_provider() { return &timezone_provider_; }
// Update requests count and last request time.
void RecordAttempt();
// This is called by TZRequest. Destroys active request and starts a new one.
void RequestIsFinished();
void ApplyTimeZone(const TimeZoneResponseData* timezone);
TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
return resolver_->delay_network_call();
}
base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> AsWeakPtr();
private:
const TimeZoneResolver* resolver_;
// Returns delay to next timezone update request
base::TimeDelta CalculateNextInterval();
SimpleGeolocationProvider geolocation_provider_;
TimeZoneProvider timezone_provider_;
base::OneShotTimer refresh_timer_;
// Total number of request attempts.
int requests_count_;
// This is not NULL when update is in progress.
scoped_ptr<TZRequest> request_;
base::WeakPtrFactory<TimeZoneResolver::TimeZoneResolverImpl>
weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
};
namespace {
// This class implements a single timezone refresh attempt.
class TZRequest {
public:
explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
: resolver_(resolver), weak_ptr_factory_(this) {}
~TZRequest();
// Starts request after specified delay.
void Start();
// Called from SimpleGeolocationProvider when location is resolved.
void OnLocationResolved(const Geoposition& position,
bool server_error,
const base::TimeDelta elapsed);
// TimeZoneRequest::TimeZoneResponseCallback implementation.
void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
bool server_error);
base::WeakPtr<TZRequest> AsWeakPtr();
private:
// This is called by network detector when network is available.
void StartRequestOnNetworkAvailable();
TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
base::WeakPtrFactory<TZRequest> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(TZRequest);
};
TZRequest::~TZRequest() {
}
void TZRequest::StartRequestOnNetworkAvailable() {
resolver_->RecordAttempt();
resolver_->geolocation_provider()->RequestGeolocation(
base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds),
base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr()));
}
void TZRequest::Start() {
// call to chromeos::DelayNetworkCall
resolver_->delay_network_call().Run(
base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr()));
}
void TZRequest::OnLocationResolved(const Geoposition& position,
bool server_error,
const base::TimeDelta elapsed) {
base::ScopedClosureRunner on_request_finished(
base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
base::Unretained(resolver_)));
// Ignore invalid position.
if (!position.Valid())
return;
const base::TimeDelta timeout =
base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds);
if (elapsed >= timeout) {
VLOG(1) << "Refresh TimeZone: got location after timeout ("
<< elapsed.InSecondsF() << " seconds elapsed). Ignored.";
return;
}
resolver_->timezone_provider()->RequestTimezone(
position,
timeout - elapsed,
base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr()));
// Prevent |on_request_finished| from firing here.
base::Closure unused = on_request_finished.Release();
}
void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
bool server_error) {
base::ScopedClosureRunner on_request_finished(
base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
base::Unretained(resolver_)));
DCHECK(timezone);
VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug()
<< "}.";
if (timezone->status != TimeZoneResponseData::OK) {
VLOG(1) << "Refresh TimeZone: failed to resolve timezone.";
return;
}
resolver_->ApplyTimeZone(timezone.get());
}
base::WeakPtr<TZRequest> TZRequest::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
} // anonymous namespace
// ------------------------------------------------------------------------
// TimeZoneResolver::TimeZoneResolverImpl implementation.
TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl(
const TimeZoneResolver* resolver)
: resolver_(resolver),
geolocation_provider_(
resolver->context().get(),
SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
timezone_provider_(resolver->context().get(),
DefaultTimezoneProviderURL()),
requests_count_(0),
weak_ptr_factory_(this) {
DCHECK(!resolver_->apply_timezone().is_null());
DCHECK(!resolver_->delay_network_call().is_null());
base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
power_monitor->AddObserver(this);
const int64 last_refresh_at_raw =
resolver_->local_state()->GetInt64(kLastTimeZoneRefreshTime);
const base::Time last_refresh_at =
base::Time::FromInternalValue(last_refresh_at_raw);
const base::Time next_refresh_not_before =
last_refresh_at +
base::TimeDelta::FromSecondsD(kRefreshTimeZoneMinimumDelayOnRestartSec);
if (next_refresh_not_before > base::Time::Now()) {
requests_count_ = kRefreshTimeZoneInitialRequestCountOnRateLimit;
VLOG(1) << "TimeZoneResolverImpl(): initialize requests_count_="
<< requests_count_ << " because of rate limit.";
}
}
TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
if (power_monitor)
power_monitor->RemoveObserver(this);
}
void TimeZoneResolver::TimeZoneResolverImpl::Start() {
// Start() is usually called twice:
// - On device boot.
// - On user session start.
if (request_ || refresh_timer_.IsRunning())
return;
ScheduleRequest();
}
// Returns delay to next timezone update request
base::TimeDelta
TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() {
// This is initial request, which should be served immediately.
if (requests_count_ == 0) {
return base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec);
}
// See comment to kRefreshIntervalRequestsCountMultiplier.
if (requests_count_ >=
MaxRequestsCountForInterval(kMaximumRefreshIntervalSec)) {
return base::TimeDelta::FromSecondsD(kMaximumRefreshIntervalSec);
}
const int base_interval = IntervalForNextRequest(requests_count_);
DCHECK_LE(base_interval, kMaximumRefreshIntervalSec);
// Add jitter to level request rate.
const base::TimeDelta interval(
base::TimeDelta::FromSecondsD(base::RandDouble() * 2 * base_interval));
VLOG(1) << "TimeZoneResolverImpl::CalculateNextInterval(): interval="
<< interval.InSecondsF();
return interval;
}
void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
requests_count_ = 0;
// Refresh timezone immediately.
request_.reset();
ScheduleRequest();
}
void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
if (request_)
return;
// base::OneShotTimer
base::TimeDelta interval = CalculateNextInterval();
refresh_timer_.Stop();
refresh_timer_.Start(
FROM_HERE, interval,
base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest,
AsWeakPtr()));
}
void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() {
if (request_)
return;
refresh_timer_.Stop();
request_.reset(new TZRequest(this));
request_->Start();
}
void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
resolver_->local_state()->SetInt64(kLastTimeZoneRefreshTime,
base::Time::Now().ToInternalValue());
++requests_count_;
}
void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() {
request_.reset();
ScheduleRequest();
}
void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
const TimeZoneResponseData* timezone) {
resolver_->apply_timezone().Run(timezone);
}
base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl>
TimeZoneResolver::TimeZoneResolverImpl::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
// ------------------------------------------------------------------------
// TimeZoneResolver implementation
TimeZoneResolver::TimeZoneResolver(
scoped_refptr<net::URLRequestContextGetter> context,
const GURL& url,
const ApplyTimeZoneCallback& apply_timezone,
const DelayNetworkCallClosure& delay_network_call,
PrefService* local_state)
: context_(context),
url_(url),
apply_timezone_(apply_timezone),
delay_network_call_(delay_network_call),
local_state_(local_state) {
DCHECK(!apply_timezone.is_null());
}
TimeZoneResolver::~TimeZoneResolver() {
Stop();
}
void TimeZoneResolver::Start() {
DCHECK(thread_checker_.CalledOnValidThread());
if (!implementation_) {
implementation_.reset(new TimeZoneResolverImpl(this));
implementation_->Start();
}
}
void TimeZoneResolver::Stop() {
DCHECK(thread_checker_.CalledOnValidThread());
implementation_.reset();
}
// static
int TimeZoneResolver::MaxRequestsCountForIntervalForTesting(
const double interval_seconds) {
return MaxRequestsCountForInterval(interval_seconds);
}
// static
int TimeZoneResolver::IntervalForNextRequestForTesting(const int requests) {
return IntervalForNextRequest(requests);
}
// static
void TimeZoneResolver::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterInt64Pref(kLastTimeZoneRefreshTime, 0);
}
} // namespace chromeos