Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9285)

Unified Diff: chromeos/timezone/timezone_resolver.cc

Issue 834073002: ChromeOS: Implement periodic timezone refresh on geolocation data. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Comment updated. Created 5 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..bd880e8ee07fb1c6c9e08c803da111a142093a45
--- /dev/null
+++ b/chromeos/timezone/timezone_resolver.cc
@@ -0,0 +1,367 @@
+// 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;
+
+// Timezone refresh happens at least each this interval.
stevenjb 2015/01/05 17:34:21 'at least once each interval'
Alexander Alekseev 2015/01/15 18:59:03 Done.
+const int64 kMaximalRefreshIntervalMS = 6 * 3600 * 1000; // 6 hours
+
+// Delay between refresh attempts depends on current number of requests and
+// this constant. If [kRefreshIntervalBasePower=3], (base is always 2),
+// then [base_multiplier = (2 << kRefreshIntervalBasePower) = 16], and
+// this gives [interval = initial_interval * (base_multiplier << requests_count)
+// = initial_interval * (16 << requests_count_)] in seconds.
stevenjb 2015/01/05 17:34:21 Is this level of complexity really necessary? Coul
Alexander Alekseev 2015/01/15 18:59:03 (2 ^ requests_count) was my initial implementation
+const unsigned long kRefreshIntervalBasePower = 3;
+
+} // anonymous namespace
+
+// This class periodically refreshes location and timezone.
+// It should be destroyed to stop refresh.
+class TimeZoneResolver::TimeZoneResolverImpl
+ : public base::PowerObserver,
+ public base::SupportsWeakPtr<TimeZoneResolver::TimeZoneResolverImpl> {
+ 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_; }
stevenjb 2015/01/05 17:34:21 Can either of these be const?
Alexander Alekseev 2015/01/15 18:59:03 It would require |timezone_provider_| to be a scop
stevenjb 2015/01/20 18:30:42 Why would it need to be scoped to be const? If all
+
+ // Update requests count and last request time.
+ void RecordAttempt();
+
+ TZRequest* ReleaseRequest();
+
+ void ApplyTimeZone(const TimeZoneResponseData* timezone);
+
+ TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
+ return delay_network_call_;
+ }
+
+ 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 requst attempts.
Bernhard Bauer 2015/01/05 10:22:33 Nit: "request"
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ unsigned int requests_count_;
+
+ // This is not NULL when update is in progress.
+ scoped_ptr<TZRequest> request_;
+
+ DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
+};
+
+namespace {
+
+// This class implements a single timezone refresh attempt.
+class TZRequest : public base::SupportsWeakPtr<TZRequest> {
+ public:
+ explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
+ : resolver_(resolver) {}
+
+ ~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);
+
+ private:
+ // Schedules new request and destroys current object.
+ void RequestIsFinished();
+
+ // This is called by network detector when network is available.
+ void StartRequestOnNetworkAvailable();
+
+ TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
+
+ 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(&TZRequest::RequestIsFinished, base::Unretained(this)));
+
+ // Ignore invalid position.
+ if (!position.Valid()) {
Bernhard Bauer 2015/01/05 10:22:33 No braces for one-line bodies.
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ 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()));
+
+ base::Closure unused = on_request_finished.Release();
stevenjb 2015/01/05 17:34:21 nit: Add a comment for those who aren't familiar w
Alexander Alekseev 2015/01/15 18:59:03 Done.
+}
+
+void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
+ bool server_error) {
+ base::ScopedClosureRunner on_request_finished(
+ base::Bind(&TZRequest::RequestIsFinished, base::Unretained(this)));
+
+ DCHECK(timezone.get());
+ 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());
+}
+
+void TZRequest::RequestIsFinished() {
+ scoped_ptr<TZRequest> self(resolver_->ReleaseRequest());
Bernhard Bauer 2015/01/05 10:22:33 All this method does is call methods on the resolv
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ resolver_->ScheduleRequest();
+ // This object is destroyed here.
+}
+
+} // 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) {
+ DCHECK(!apply_timezone.is_null());
+ DCHECK(!delay_network_call.is_null());
+
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
+ DCHECK(power_monitor);
Bernhard Bauer 2015/01/05 10:22:33 This DCHECK isn't really necessary; if |power_moni
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ power_monitor->AddObserver(this);
+}
+
+TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
+ DCHECK(power_monitor);
stevenjb 2015/01/05 17:34:22 power_monitor could be NULL on shutdown, so just r
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ power_monitor->RemoveObserver(this);
+}
+
+void TimeZoneResolver::TimeZoneResolverImpl::Start() {
+ // Start() is usually called twice:
+ // - On device boot.
+ // - On user session start.
+ if (request_.get() || refresh_timer_.IsRunning())
Bernhard Bauer 2015/01/05 10:22:33 scoped_ptr has a bool overload, so you don't need
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ 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 immediate.
Bernhard Bauer 2015/01/05 10:22:33 Nit: "immediately"
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ if (requests_count_ == 0)
+ return initial_interval;
+
+ // See comment to kRefreshIntervalBasePower.
+ const unsigned long power = kRefreshIntervalBasePower;
+ const unsigned long base_multiplier = 2 << power;
+
+ if (requests_count_ >
+ ::log2(maximum_interval.InSecondsF() / initial_interval.InSecondsF()) /
+ power)
Bernhard Bauer 2015/01/05 10:22:33 Wait... You divide by |power| here. That means you
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ return maximum_interval;
+
+ const base::TimeDelta interval(base::TimeDelta::FromSeconds(
+ initial_interval.InSecondsF() * (base_multiplier << requests_count_)));
+ return std::min(interval, maximum_interval);
Bernhard Bauer 2015/01/05 10:22:33 Assuming you clamp at |maximum_interval| already a
Alexander Alekseev 2015/01/15 18:59:03 Done.
+}
+
+void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
+ requests_count_ = 0;
+ // Refresh timezone immediately.
+ request_.reset();
+ ScheduleRequest();
+}
+
+void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
+ if (request_.get())
+ 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_.get())
+ return;
+
+ refresh_timer_.Stop();
+
+ request_.reset(new TZRequest(this));
+ request_->Start();
+}
+
+void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
+ ++requests_count_;
+}
+
+TZRequest* TimeZoneResolver::TimeZoneResolverImpl::ReleaseRequest() {
+ return request_.release();
stevenjb 2015/01/05 17:34:22 This should return a scoped_ptr to ensure that own
Alexander Alekseev 2015/01/15 18:59:03 Done.
+}
+
+void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
+ const TimeZoneResponseData* timezone) {
+ apply_timezone_.Run(timezone);
+}
+
+// ------------------------------------------------------------------------
+// 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),
+ implementation_(nullptr) {
+ DCHECK(!apply_timezone.is_null());
+}
+
+TimeZoneResolver::~TimeZoneResolver() {
+ Stop();
+}
+
+void TimeZoneResolver::Start() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!implementation_) {
+ implementation_ = new TimeZoneResolverImpl(context_, url_, apply_timezone_,
+ delay_network_call_);
+ implementation_->Start();
+ }
+}
+
+void TimeZoneResolver::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (implementation_) {
Bernhard Bauer 2015/01/05 10:22:33 This seems like an excellent use case for a scoped
Alexander Alekseev 2015/01/15 18:59:03 Done.
+ delete implementation_;
+ implementation_ = nullptr;
+ }
+}
+
+} // namespace chromeos

Powered by Google App Engine
This is Rietveld 408576698