Index: chromeos/timezone/timezone_resolver.cc |
diff --git a/chromeos/timezone/timezone_resolver.cc b/chromeos/timezone/timezone_resolver.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8c636d44bf98ec66039e685f8ea8df1793d3a210 |
--- /dev/null |
+++ b/chromeos/timezone/timezone_resolver.cc |
@@ -0,0 +1,398 @@ |
+// 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/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 int64 kInitialRefreshIntervalMS = 3000; |
Dmitry Polukhin
2015/01/20 15:32:28
Please add jitter and also consider not sending re
Alexander Alekseev
2015/01/22 18:55:23
Done.
|
+ |
+// Timezone refresh happens at least once each this interval. |
+const int64 kMaximalRefreshIntervalMS = 6 * 3600 * 1000; // 6 hours |
stevenjb
2015/01/20 18:30:42
Maximal -> Maximum
Alexander Alekseev
2015/01/22 18:55:22
Done.
|
+ |
+// Delay between refresh attempts depends on current number of requests and |
+// this constant. |
+// [interval = kInitialRefreshIntervalMS * (2 ^ |
+// (kRefreshIntervalRequestsCountMultiplier * requests_count))] |
+// in seconds. |
+const unsigned long kRefreshIntervalRequestsCountMultiplier = 3; |
+ |
+int MaxRequestsCountForInterval(const double interval_seconds) { |
+ const base::TimeDelta initial_interval = |
+ base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS); |
stevenjb
2015/01/20 18:30:42
Am I missing something or is this just being used
Alexander Alekseev
2015/01/22 18:55:22
Done.
|
+ return log2(interval_seconds / initial_interval.InSecondsF()) / |
+ kRefreshIntervalRequestsCountMultiplier; |
+} |
+ |
+int IntervalForNextRequest(const int requests) { |
+ const base::TimeDelta initial_interval = |
+ base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS); |
stevenjb
2015/01/20 18:30:42
Same comment here. Do we ever use kInitialRefreshI
Alexander Alekseev
2015/01/22 18:55:23
Done.
|
+ return static_cast<int>(initial_interval.InSecondsF() * |
+ (2 << (static_cast<unsigned>(requests) * |
+ kRefreshIntervalRequestsCountMultiplier))); |
+} |
+ |
+} // anonymous namespace |
+ |
+// This class periodically refreshes location and timezone. |
+// It should be destroyed to stop refresh. |
+class TimeZoneResolver::TimeZoneResolverImpl : public base::PowerObserver { |
+ public: |
+ TimeZoneResolverImpl( |
+ net::URLRequestContextGetter* url_context_getter, |
+ const GURL& url, |
+ const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone, |
+ const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call); |
+ |
+ ~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 delay_network_call_; |
+ } |
+ |
+ base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> AsWeakPtr(); |
+ |
+ private: |
+ // Returns delay to next timezone update request |
+ base::TimeDelta CalculateNextInterval(); |
+ |
+ net::URLRequestContextGetter* url_context_getter_; |
+ const GURL url_; |
+ |
+ const TimeZoneResolver::ApplyTimeZoneCallback apply_timezone_; |
+ const TimeZoneResolver::DelayNetworkCallClosure delay_network_call_; |
+ |
+ 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( |
Dmitry Polukhin
2015/01/20 15:32:28
Please make sure that geolocation_provider does ha
Alexander Alekseev
2015/01/22 18:55:23
It has it. It makes (probably) several requests un
|
+ 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, |
+ false, // sensor |
+ 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( |
+ net::URLRequestContextGetter* url_context_getter, |
+ const GURL& url, |
+ const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone, |
+ const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call) |
+ : url_context_getter_(url_context_getter), |
+ url_(url), |
+ apply_timezone_(apply_timezone), |
+ delay_network_call_(delay_network_call), |
+ geolocation_provider_( |
+ url_context_getter, |
+ SimpleGeolocationProvider::DefaultGeolocationProviderURL()), |
+ timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()), |
+ requests_count_(0), |
+ weak_ptr_factory_(this) { |
+ DCHECK(!apply_timezone.is_null()); |
+ DCHECK(!delay_network_call.is_null()); |
+ |
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); |
+ power_monitor->AddObserver(this); |
+} |
+ |
+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() { |
+ const base::TimeDelta initial_interval = |
+ base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS); |
+ const base::TimeDelta maximum_interval = |
+ base::TimeDelta::FromMilliseconds(kMaximalRefreshIntervalMS); |
+ |
+ // This is initial request, which should be served immediately. |
+ if (requests_count_ == 0) |
+ return initial_interval; |
+ |
+ // See comment to kRefreshIntervalRequestsCountMultiplier. |
+ if (requests_count_ >= |
+ MaxRequestsCountForInterval(maximum_interval.InSecondsF())) { |
+ return maximum_interval; |
+ } |
+ |
+ const base::TimeDelta interval( |
+ base::TimeDelta::FromSeconds(IntervalForNextRequest(requests_count_))); |
+ DCHECK_LE(interval, maximum_interval); |
+ 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() { |
+ ++requests_count_; |
+} |
+ |
+void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() { |
+ request_.reset(); |
+ ScheduleRequest(); |
+} |
+ |
+void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone( |
+ const TimeZoneResponseData* timezone) { |
+ apply_timezone_.Run(timezone); |
+} |
+ |
+base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> |
+TimeZoneResolver::TimeZoneResolverImpl::AsWeakPtr() { |
+ return weak_ptr_factory_.GetWeakPtr(); |
+} |
+ |
+// ------------------------------------------------------------------------ |
+// TimeZoneResolver implementation |
+ |
+TimeZoneResolver::TimeZoneResolver( |
+ net::URLRequestContextGetter* context, |
+ const GURL& url, |
+ const ApplyTimeZoneCallback& apply_timezone, |
+ const DelayNetworkCallClosure& delay_network_call) |
+ : context_(context), |
+ url_(url), |
+ apply_timezone_(apply_timezone), |
+ delay_network_call_(delay_network_call) { |
+ DCHECK(!apply_timezone.is_null()); |
+} |
+ |
+TimeZoneResolver::~TimeZoneResolver() { |
+ Stop(); |
+} |
+ |
+void TimeZoneResolver::Start() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (!implementation_) { |
+ implementation_.reset(new TimeZoneResolverImpl( |
+ context_, url_, apply_timezone_, delay_network_call_)); |
+ 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); |
+} |
+ |
+} // namespace chromeos |