summaryrefslogtreecommitdiffstats
path: root/chromeos/timezone
diff options
context:
space:
mode:
authoralemate <alemate@chromium.org>2015-01-30 10:11:41 -0800
committerCommit bot <commit-bot@chromium.org>2015-01-30 18:13:00 +0000
commit48255f3da019c9728fc22b7007e5ca84b90cd78f (patch)
tree8d97fa204c357bbf4d36afa782f0196f57ebc020 /chromeos/timezone
parent3b2a6c62340bde336fbd6f423a40b1cb99e35e3e (diff)
downloadchromium_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/timezone')
-rw-r--r--chromeos/timezone/OWNERS1
-rw-r--r--chromeos/timezone/timezone_resolver.cc424
-rw-r--r--chromeos/timezone/timezone_resolver.h89
-rw-r--r--chromeos/timezone/timezone_unittest.cc10
4 files changed, 524 insertions, 0 deletions
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