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

Side by Side 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: Update after review. Created 5 years, 11 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chromeos/timezone/timezone_resolver.h"
6
7 #include <math.h>
8
9 #include <algorithm>
10
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/callback_helpers.h"
14 #include "base/logging.h"
15 #include "base/power_monitor/power_monitor.h"
16 #include "base/power_monitor/power_observer.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/time/time.h"
19 #include "chromeos/geolocation/geoposition.h"
20 #include "chromeos/geolocation/simple_geolocation_provider.h"
21 #include "chromeos/timezone/timezone_provider.h"
22 #include "net/url_request/url_request_context_getter.h"
23 #include "url/gurl.h"
24
25 namespace chromeos {
26
27 namespace {
28
29 class TZRequest;
30
31 // Total timezone resolving process timeout.
32 const unsigned int kRefreshTimeZoneTimeoutSeconds = 60;
33
34 // Initial delay (for the first request).
35 const int64 kInitialRefreshIntervalMS = 3000;
36
37 // Timezone refresh happens at least once each this interval.
38 const int64 kMaximalRefreshIntervalMS = 6 * 3600 * 1000; // 6 hours
39
40 // Delay between refresh attempts depends on current number of requests and
41 // this constant.
42 // [interval = kInitialRefreshIntervalMS * (2 ^
43 // (kRefreshIntervalRequestsCountMultiplier * requests_count))]
44 // in seconds.
Bernhard Bauer 2015/01/16 10:56:11 BTW, I just found out there's also net::BackoffEnt
Alexander Alekseev 2015/01/16 20:42:44 It implements back-off strategy to optimize for ne
45 const unsigned long kRefreshIntervalRequestsCountMultiplier = 3;
46
47 unsigned int MaxRequestsCountForInterval(const double interval_seconds) {
48 const base::TimeDelta initial_interval =
49 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS);
50 return log2(interval_seconds / initial_interval.InSecondsF()) /
51 kRefreshIntervalRequestsCountMultiplier;
52 }
53
54 unsigned int IntervalForNextRequest(const unsigned int requests) {
55 const base::TimeDelta initial_interval =
56 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS);
57 return static_cast<unsigned int>(
58 initial_interval.InSecondsF() *
59 (2 << (requests * kRefreshIntervalRequestsCountMultiplier)));
60 }
61
62 } // anonymous namespace
63
64 // This class periodically refreshes location and timezone.
65 // It should be destroyed to stop refresh.
66 class TimeZoneResolver::TimeZoneResolverImpl
67 : public base::PowerObserver,
68 public base::SupportsWeakPtr<TimeZoneResolver::TimeZoneResolverImpl> {
69 public:
70 TimeZoneResolverImpl(
71 net::URLRequestContextGetter* url_context_getter,
72 const GURL& url,
73 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
74 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call);
75
76 ~TimeZoneResolverImpl() override;
77
78 // This is called once after the object is created.
79 void Start();
80
81 // PowerObserver implementation.
82 void OnResume() override;
83
84 // (Re)Starts timer.
85 void ScheduleRequest();
86
87 // Creates new TZRequest.
88 void CreateNewRequest();
89
90 // Called by TZRequest.
91 SimpleGeolocationProvider* geolocation_provider() {
92 return &geolocation_provider_;
93 }
94 TimeZoneProvider* timezone_provider() { return &timezone_provider_; }
95
96 // Update requests count and last request time.
97 void RecordAttempt();
98
99 // This is called by TZRequest. Destroys active request and starts a new one.
100 void RequestIsFinished();
101
102 void ApplyTimeZone(const TimeZoneResponseData* timezone);
103
104 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
105 return delay_network_call_;
106 }
107
108 private:
109 // Returns delay to next timezone update request
110 base::TimeDelta CalculateNextInterval();
111
112 net::URLRequestContextGetter* url_context_getter_;
113 const GURL url_;
114
115 const TimeZoneResolver::ApplyTimeZoneCallback apply_timezone_;
116 const TimeZoneResolver::DelayNetworkCallClosure delay_network_call_;
117
118 SimpleGeolocationProvider geolocation_provider_;
119 TimeZoneProvider timezone_provider_;
120
121 base::OneShotTimer<TimeZoneResolver::TimeZoneResolverImpl> refresh_timer_;
122
123 // Total number of request attempts.
124 unsigned int requests_count_;
125
126 // This is not NULL when update is in progress.
127 scoped_ptr<TZRequest> request_;
128
129 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
130 };
131
132 namespace {
133
134 // This class implements a single timezone refresh attempt.
135 class TZRequest : public base::SupportsWeakPtr<TZRequest> {
136 public:
137 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
138 : resolver_(resolver) {}
139
140 ~TZRequest();
141
142 // Starts request after specified delay.
143 void Start();
144
145 // Called from SimpleGeolocationProvider when location is resolved.
146 void OnLocationResolved(const Geoposition& position,
147 bool server_error,
148 const base::TimeDelta elapsed);
149
150 // TimeZoneRequest::TimeZoneResponseCallback implementation.
151 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
152 bool server_error);
153
154 private:
155 // Schedules new request and destroys current object.
156 void RequestIsFinished();
157
158 // This is called by network detector when network is available.
159 void StartRequestOnNetworkAvailable();
160
161 TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
162
163 DISALLOW_COPY_AND_ASSIGN(TZRequest);
164 };
165
166 TZRequest::~TZRequest() {
167 }
168
169 void TZRequest::StartRequestOnNetworkAvailable() {
170 resolver_->RecordAttempt();
171 resolver_->geolocation_provider()->RequestGeolocation(
172 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds),
173 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr()));
174 }
175
176 void TZRequest::Start() {
177 // call to chromeos::DelayNetworkCall
178 resolver_->delay_network_call().Run(
179 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr()));
180 }
181
182 void TZRequest::OnLocationResolved(const Geoposition& position,
183 bool server_error,
184 const base::TimeDelta elapsed) {
185 base::ScopedClosureRunner on_request_finished(
186 base::Bind(&TZRequest::RequestIsFinished, base::Unretained(this)));
187
188 // Ignore invalid position.
189 if (!position.Valid())
190 return;
191
192 const base::TimeDelta timeout =
193 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds);
194
195 if (elapsed >= timeout) {
196 VLOG(1) << "Refresh TimeZone: got location after timeout ("
197 << elapsed.InSecondsF() << " seconds elapsed). Ignored.";
198 return;
199 }
200
201 resolver_->timezone_provider()->RequestTimezone(
202 position,
203 false, // sensor
204 timeout - elapsed,
205 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr()));
206
207 // Prevent |on_request_finished| from firing here.
208 base::Closure unused = on_request_finished.Release();
209 }
210
211 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
212 bool server_error) {
213 base::ScopedClosureRunner on_request_finished(
214 base::Bind(&TZRequest::RequestIsFinished, base::Unretained(this)));
215
216 DCHECK(timezone);
217 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug()
218 << "}.";
219
220 if (timezone->status != TimeZoneResponseData::OK) {
221 VLOG(1) << "Refresh TimeZone: failed to resolve timezone.";
222 return;
223 }
224
225 resolver_->ApplyTimeZone(timezone.get());
226 }
227
228 void TZRequest::RequestIsFinished() {
Bernhard Bauer 2015/01/16 09:25:05 You only call this via a closure now, right? In th
Alexander Alekseev 2015/01/16 20:42:44 Done.
229 // This object is destroyed inside TimeZoneResolverImpl::RequestIsFinished().
230 resolver_->RequestIsFinished();
231 }
232
233 } // anonymous namespace
234
235 // ------------------------------------------------------------------------
236 // TimeZoneResolver::TimeZoneResolverImpl implementation.
237
238 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl(
239 net::URLRequestContextGetter* url_context_getter,
240 const GURL& url,
241 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
242 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call)
243 : url_context_getter_(url_context_getter),
244 url_(url),
245 apply_timezone_(apply_timezone),
246 delay_network_call_(delay_network_call),
247 geolocation_provider_(
248 url_context_getter,
249 SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
250 timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()),
251 requests_count_(0) {
252 DCHECK(!apply_timezone.is_null());
253 DCHECK(!delay_network_call.is_null());
254
255 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
256 power_monitor->AddObserver(this);
257 }
258
259 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
260 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
261 if (power_monitor)
262 power_monitor->RemoveObserver(this);
263 }
264
265 void TimeZoneResolver::TimeZoneResolverImpl::Start() {
266 // Start() is usually called twice:
267 // - On device boot.
268 // - On user session start.
269 if (request_ || refresh_timer_.IsRunning())
270 return;
271
272 ScheduleRequest();
273 }
274
275 // Returns delay to next timezone update request
276 base::TimeDelta
277 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() {
278 const base::TimeDelta initial_interval =
279 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS);
280 const base::TimeDelta maximum_interval =
281 base::TimeDelta::FromMilliseconds(kMaximalRefreshIntervalMS);
282
283 // This is initial request, which should be served immediately.
284 if (requests_count_ == 0)
285 return initial_interval;
286
287 // See comment to kRefreshIntervalRequestsCountMultiplier.
288 if (requests_count_ >=
289 MaxRequestsCountForInterval(maximum_interval.InSecondsF())) {
290 return maximum_interval;
291 }
292
293 const base::TimeDelta interval(
294 base::TimeDelta::FromSeconds(IntervalForNextRequest(requests_count_)));
295 DCHECK_LE(interval, maximum_interval);
296 return interval;
297 }
298
299 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
300 requests_count_ = 0;
301 // Refresh timezone immediately.
302 request_.reset();
303 ScheduleRequest();
304 }
305
306 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
307 if (request_)
308 return;
309
310 // base::OneShotTimer
311 base::TimeDelta interval = CalculateNextInterval();
312 refresh_timer_.Stop();
313 refresh_timer_.Start(
314 FROM_HERE, interval,
315 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest,
316 AsWeakPtr()));
317 }
318
319 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() {
320 if (request_)
321 return;
322
323 refresh_timer_.Stop();
324
325 request_.reset(new TZRequest(this));
326 request_->Start();
327 }
328
329 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
330 ++requests_count_;
331 }
332
333 void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() {
334 scoped_ptr<TZRequest> previous_request(request_.release());
Bernhard Bauer 2015/01/16 09:25:05 Is it important now that the request is only destr
Alexander Alekseev 2015/01/16 20:42:44 Done.
335 ScheduleRequest();
336 }
337
338 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
339 const TimeZoneResponseData* timezone) {
340 apply_timezone_.Run(timezone);
341 }
342
343 // ------------------------------------------------------------------------
344 // TimeZoneResolver implementation
345
346 TimeZoneResolver::TimeZoneResolver(
347 net::URLRequestContextGetter* context,
348 const GURL& url,
349 const ApplyTimeZoneCallback& apply_timezone,
350 const DelayNetworkCallClosure& delay_network_call)
351 : context_(context),
352 url_(url),
353 apply_timezone_(apply_timezone),
354 delay_network_call_(delay_network_call) {
355 DCHECK(!apply_timezone.is_null());
356 }
357
358 TimeZoneResolver::~TimeZoneResolver() {
359 Stop();
360 }
361
362 void TimeZoneResolver::Start() {
363 DCHECK(thread_checker_.CalledOnValidThread());
364 if (!implementation_) {
365 implementation_.reset(new TimeZoneResolverImpl(
366 context_, url_, apply_timezone_, delay_network_call_));
367 implementation_->Start();
368 }
369 }
370
371 void TimeZoneResolver::Stop() {
372 DCHECK(thread_checker_.CalledOnValidThread());
373 implementation_.reset();
374 }
375
376 unsigned int MaxRequestsCountForIntervalForTesting(
377 const double interval_seconds) {
378 return MaxRequestsCountForInterval(interval_seconds);
379 }
380
381 unsigned int IntervalForNextRequestForTesting(const unsigned int requests) {
382 return IntervalForNextRequest(requests);
383 }
384
385 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698