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