diff options
author | alemate <alemate@chromium.org> | 2015-01-30 10:11:41 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-01-30 18:13:00 +0000 |
commit | 48255f3da019c9728fc22b7007e5ca84b90cd78f (patch) | |
tree | 8d97fa204c357bbf4d36afa782f0196f57ebc020 /chromeos | |
parent | 3b2a6c62340bde336fbd6f423a40b1cb99e35e3e (diff) | |
download | chromium_src-48255f3da019c9728fc22b7007e5ca84b90cd78f.zip chromium_src-48255f3da019c9728fc22b7007e5ca84b90cd78f.tar.gz chromium_src-48255f3da019c9728fc22b7007e5ca84b90cd78f.tar.bz2 |
ChromeOS: Implement periodic timezone refresh on geolocation data.
This CL implements automatic timezone refresh on location update.
BUG=416494
TEST=manually tested
Review URL: https://codereview.chromium.org/834073002
Cr-Commit-Position: refs/heads/master@{#313943}
Diffstat (limited to 'chromeos')
-rw-r--r-- | chromeos/chromeos.gyp | 4 | ||||
-rw-r--r-- | chromeos/chromeos_switches.cc | 3 | ||||
-rw-r--r-- | chromeos/chromeos_switches.h | 1 | ||||
-rw-r--r-- | chromeos/timezone/OWNERS | 1 | ||||
-rw-r--r-- | chromeos/timezone/timezone_resolver.cc | 424 | ||||
-rw-r--r-- | chromeos/timezone/timezone_resolver.h | 89 | ||||
-rw-r--r-- | chromeos/timezone/timezone_unittest.cc | 10 |
7 files changed, 531 insertions, 1 deletions
diff --git a/chromeos/chromeos.gyp b/chromeos/chromeos.gyp index 1e85489..c6206ed 100644 --- a/chromeos/chromeos.gyp +++ b/chromeos/chromeos.gyp @@ -419,10 +419,12 @@ 'system/statistics_provider.h', 'system/version_loader.cc', 'system/version_loader.h', - 'timezone/timezone_provider.h', 'timezone/timezone_provider.cc', + 'timezone/timezone_provider.h', 'timezone/timezone_request.cc', 'timezone/timezone_request.h', + 'timezone/timezone_resolver.cc', + 'timezone/timezone_resolver.h', 'tpm/tpm_password_fetcher.cc', 'tpm/tpm_password_fetcher.h', 'tpm/tpm_token_info_getter.cc', diff --git a/chromeos/chromeos_switches.cc b/chromeos/chromeos_switches.cc index 0362e5c..20c55c8 100644 --- a/chromeos/chromeos_switches.cc +++ b/chromeos/chromeos_switches.cc @@ -304,6 +304,9 @@ const char kArtifactsDir[] = "artifacts-dir"; const char kEnableCaptivePortalBypassProxy[] = "enable-captive-portal-bypass-proxy"; +// Enable automatic timezone update. +const char kEnableTimeZoneTrackingOption[] = "enable-timezone-tracking-option"; + bool WakeOnWifiEnabled() { return !base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableWakeOnWifi); } diff --git a/chromeos/chromeos_switches.h b/chromeos/chromeos_switches.h index 345889d..1ddddeb 100644 --- a/chromeos/chromeos_switches.h +++ b/chromeos/chromeos_switches.h @@ -99,6 +99,7 @@ CHROMEOS_EXPORT extern const char kSystemDevMode[]; CHROMEOS_EXPORT extern const char kTestAutoUpdateUI[]; CHROMEOS_EXPORT extern const char kWakeOnPackets[]; CHROMEOS_EXPORT extern const char kEnableCaptivePortalBypassProxy[]; +CHROMEOS_EXPORT extern const char kEnableTimeZoneTrackingOption[]; CHROMEOS_EXPORT bool WakeOnWifiEnabled(); diff --git a/chromeos/timezone/OWNERS b/chromeos/timezone/OWNERS new file mode 100644 index 0000000..2dcc458 --- /dev/null +++ b/chromeos/timezone/OWNERS @@ -0,0 +1 @@ +alemate@chromium.org diff --git a/chromeos/timezone/timezone_resolver.cc b/chromeos/timezone/timezone_resolver.cc new file mode 100644 index 0000000..72c3976 --- /dev/null +++ b/chromeos/timezone/timezone_resolver.cc @@ -0,0 +1,424 @@ +// 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.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" +#include "net/url_request/url_request_context_getter.h" +#include "url/gurl.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<TimeZoneResolver::TimeZoneResolverImpl> 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 diff --git a/chromeos/timezone/timezone_resolver.h b/chromeos/timezone/timezone_resolver.h new file mode 100644 index 0000000..231575b --- /dev/null +++ b/chromeos/timezone/timezone_resolver.h @@ -0,0 +1,89 @@ +// 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. + +#ifndef CHROMEOS_TIMEZONE_TIMEZONE_RESOLVER_H_ +#define CHROMEOS_TIMEZONE_TIMEZONE_RESOLVER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "chromeos/chromeos_export.h" +#include "net/url_request/url_request_context_getter.h" +#include "url/gurl.h" + +class PrefRegistrySimple; +class PrefService; + +namespace chromeos { + +struct TimeZoneResponseData; + +// This class implements periodic timezone synchronization. +class CHROMEOS_EXPORT TimeZoneResolver { + public: + class TimeZoneResolverImpl; + + // This callback will be called when new timezone arrives. + using ApplyTimeZoneCallback = + base::Callback<void(const TimeZoneResponseData*)>; + + // chromeos::DelayNetworkCall cannot be used directly due to link + // restrictions. + using DelayNetworkCallClosure = base::Callback<void(const base::Closure&)>; + + // This is a LocalState preference to store base::Time value of the last + // request. It is used to limit request rate on browser restart. + static const char kLastTimeZoneRefreshTime[]; + + TimeZoneResolver(scoped_refptr<net::URLRequestContextGetter> context, + const GURL& url, + const ApplyTimeZoneCallback& apply_timezone, + const DelayNetworkCallClosure& delay_network_call, + PrefService* local_state); + ~TimeZoneResolver(); + + // Starts periodic timezone refresh. + void Start(); + + // Cancels current request and stops periodic timezone refresh. + void Stop(); + + // Register prefs to LocalState. + static void RegisterPrefs(PrefRegistrySimple* registry); + + scoped_refptr<net::URLRequestContextGetter> context() const { + return context_; + } + + DelayNetworkCallClosure delay_network_call() const { + return delay_network_call_; + } + + ApplyTimeZoneCallback apply_timezone() const { return apply_timezone_; } + + PrefService* local_state() const { return local_state_; } + + // Expose internal fuctions for testing. + static int MaxRequestsCountForIntervalForTesting( + const double interval_seconds); + static int IntervalForNextRequestForTesting(const int requests); + + private: + scoped_refptr<net::URLRequestContextGetter> context_; + const GURL url_; + + const ApplyTimeZoneCallback apply_timezone_; + const DelayNetworkCallClosure delay_network_call_; + PrefService* local_state_; + + scoped_ptr<TimeZoneResolverImpl> implementation_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(TimeZoneResolver); +}; + +} // namespace chromeos + +#endif // CHROMEOS_TIMEZONE_TIMEZONE_RESOLVER_H_ diff --git a/chromeos/timezone/timezone_unittest.cc b/chromeos/timezone/timezone_unittest.cc index 392e9af..9e05fe0 100644 --- a/chromeos/timezone/timezone_unittest.cc +++ b/chromeos/timezone/timezone_unittest.cc @@ -6,6 +6,7 @@ #include "base/run_loop.h" #include "chromeos/geolocation/geoposition.h" #include "chromeos/timezone/timezone_provider.h" +#include "chromeos/timezone/timezone_resolver.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "net/url_request/test_url_fetcher_factory.h" @@ -284,4 +285,13 @@ TEST_F(TimeZoneTest, InvalidResponse) { } } +TEST(TimeZoneResolverTest, CheckIntervals) { + for (int requests_count = 1; requests_count < 10; ++requests_count) { + EXPECT_EQ(requests_count, + TimeZoneResolver::MaxRequestsCountForIntervalForTesting( + TimeZoneResolver::IntervalForNextRequestForTesting( + requests_count))); + } +} + } // namespace chromeos |