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

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: Cleanup. 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;
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.
36
37 // Timezone refresh happens at least once each this interval.
38 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.
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.
45 const unsigned long kRefreshIntervalRequestsCountMultiplier = 3;
46
47 int MaxRequestsCountForInterval(const double interval_seconds) {
48 const base::TimeDelta initial_interval =
49 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.
50 return log2(interval_seconds / initial_interval.InSecondsF()) /
51 kRefreshIntervalRequestsCountMultiplier;
52 }
53
54 int IntervalForNextRequest(const int requests) {
55 const base::TimeDelta initial_interval =
56 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.
57 return static_cast<int>(initial_interval.InSecondsF() *
58 (2 << (static_cast<unsigned>(requests) *
59 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 : public base::PowerObserver {
67 public:
68 TimeZoneResolverImpl(
69 net::URLRequestContextGetter* url_context_getter,
70 const GURL& url,
71 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
72 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call);
73
74 ~TimeZoneResolverImpl() override;
75
76 // This is called once after the object is created.
77 void Start();
78
79 // PowerObserver implementation.
80 void OnResume() override;
81
82 // (Re)Starts timer.
83 void ScheduleRequest();
84
85 // Creates new TZRequest.
86 void CreateNewRequest();
87
88 // Called by TZRequest.
89 SimpleGeolocationProvider* geolocation_provider() {
90 return &geolocation_provider_;
91 }
92 TimeZoneProvider* timezone_provider() { return &timezone_provider_; }
93
94 // Update requests count and last request time.
95 void RecordAttempt();
96
97 // This is called by TZRequest. Destroys active request and starts a new one.
98 void RequestIsFinished();
99
100 void ApplyTimeZone(const TimeZoneResponseData* timezone);
101
102 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
103 return delay_network_call_;
104 }
105
106 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> AsWeakPtr();
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 int requests_count_;
125
126 // This is not NULL when update is in progress.
127 scoped_ptr<TZRequest> request_;
128
129 base::WeakPtrFactory<TimeZoneResolver::TimeZoneResolverImpl>
130 weak_ptr_factory_;
131
132 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
133 };
134
135 namespace {
136
137 // This class implements a single timezone refresh attempt.
138 class TZRequest {
139 public:
140 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
141 : resolver_(resolver), weak_ptr_factory_(this) {}
142
143 ~TZRequest();
144
145 // Starts request after specified delay.
146 void Start();
147
148 // Called from SimpleGeolocationProvider when location is resolved.
149 void OnLocationResolved(const Geoposition& position,
150 bool server_error,
151 const base::TimeDelta elapsed);
152
153 // TimeZoneRequest::TimeZoneResponseCallback implementation.
154 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
155 bool server_error);
156
157 base::WeakPtr<TZRequest> AsWeakPtr();
158
159 private:
160 // This is called by network detector when network is available.
161 void StartRequestOnNetworkAvailable();
162
163 TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
164
165 base::WeakPtrFactory<TZRequest> weak_ptr_factory_;
166
167 DISALLOW_COPY_AND_ASSIGN(TZRequest);
168 };
169
170 TZRequest::~TZRequest() {
171 }
172
173 void TZRequest::StartRequestOnNetworkAvailable() {
174 resolver_->RecordAttempt();
175 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
176 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds),
177 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr()));
178 }
179
180 void TZRequest::Start() {
181 // call to chromeos::DelayNetworkCall
182 resolver_->delay_network_call().Run(
183 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr()));
184 }
185
186 void TZRequest::OnLocationResolved(const Geoposition& position,
187 bool server_error,
188 const base::TimeDelta elapsed) {
189 base::ScopedClosureRunner on_request_finished(
190 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
191 base::Unretained(resolver_)));
192
193 // Ignore invalid position.
194 if (!position.Valid())
195 return;
196
197 const base::TimeDelta timeout =
198 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds);
199
200 if (elapsed >= timeout) {
201 VLOG(1) << "Refresh TimeZone: got location after timeout ("
202 << elapsed.InSecondsF() << " seconds elapsed). Ignored.";
203 return;
204 }
205
206 resolver_->timezone_provider()->RequestTimezone(
207 position,
208 false, // sensor
209 timeout - elapsed,
210 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr()));
211
212 // Prevent |on_request_finished| from firing here.
213 base::Closure unused = on_request_finished.Release();
214 }
215
216 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
217 bool server_error) {
218 base::ScopedClosureRunner on_request_finished(
219 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
220 base::Unretained(resolver_)));
221
222 DCHECK(timezone);
223 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug()
224 << "}.";
225
226 if (timezone->status != TimeZoneResponseData::OK) {
227 VLOG(1) << "Refresh TimeZone: failed to resolve timezone.";
228 return;
229 }
230
231 resolver_->ApplyTimeZone(timezone.get());
232 }
233
234 base::WeakPtr<TZRequest> TZRequest::AsWeakPtr() {
235 return weak_ptr_factory_.GetWeakPtr();
236 }
237
238 } // anonymous namespace
239
240 // ------------------------------------------------------------------------
241 // TimeZoneResolver::TimeZoneResolverImpl implementation.
242
243 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl(
244 net::URLRequestContextGetter* url_context_getter,
245 const GURL& url,
246 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
247 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call)
248 : url_context_getter_(url_context_getter),
249 url_(url),
250 apply_timezone_(apply_timezone),
251 delay_network_call_(delay_network_call),
252 geolocation_provider_(
253 url_context_getter,
254 SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
255 timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()),
256 requests_count_(0),
257 weak_ptr_factory_(this) {
258 DCHECK(!apply_timezone.is_null());
259 DCHECK(!delay_network_call.is_null());
260
261 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
262 power_monitor->AddObserver(this);
263 }
264
265 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
266 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
267 if (power_monitor)
268 power_monitor->RemoveObserver(this);
269 }
270
271 void TimeZoneResolver::TimeZoneResolverImpl::Start() {
272 // Start() is usually called twice:
273 // - On device boot.
274 // - On user session start.
275 if (request_ || refresh_timer_.IsRunning())
276 return;
277
278 ScheduleRequest();
279 }
280
281 // Returns delay to next timezone update request
282 base::TimeDelta
283 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() {
284 const base::TimeDelta initial_interval =
285 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS);
286 const base::TimeDelta maximum_interval =
287 base::TimeDelta::FromMilliseconds(kMaximalRefreshIntervalMS);
288
289 // This is initial request, which should be served immediately.
290 if (requests_count_ == 0)
291 return initial_interval;
292
293 // See comment to kRefreshIntervalRequestsCountMultiplier.
294 if (requests_count_ >=
295 MaxRequestsCountForInterval(maximum_interval.InSecondsF())) {
296 return maximum_interval;
297 }
298
299 const base::TimeDelta interval(
300 base::TimeDelta::FromSeconds(IntervalForNextRequest(requests_count_)));
301 DCHECK_LE(interval, maximum_interval);
302 return interval;
303 }
304
305 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
306 requests_count_ = 0;
307 // Refresh timezone immediately.
308 request_.reset();
309 ScheduleRequest();
310 }
311
312 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
313 if (request_)
314 return;
315
316 // base::OneShotTimer
317 base::TimeDelta interval = CalculateNextInterval();
318 refresh_timer_.Stop();
319 refresh_timer_.Start(
320 FROM_HERE, interval,
321 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest,
322 AsWeakPtr()));
323 }
324
325 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() {
326 if (request_)
327 return;
328
329 refresh_timer_.Stop();
330
331 request_.reset(new TZRequest(this));
332 request_->Start();
333 }
334
335 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
336 ++requests_count_;
337 }
338
339 void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() {
340 request_.reset();
341 ScheduleRequest();
342 }
343
344 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
345 const TimeZoneResponseData* timezone) {
346 apply_timezone_.Run(timezone);
347 }
348
349 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl>
350 TimeZoneResolver::TimeZoneResolverImpl::AsWeakPtr() {
351 return weak_ptr_factory_.GetWeakPtr();
352 }
353
354 // ------------------------------------------------------------------------
355 // TimeZoneResolver implementation
356
357 TimeZoneResolver::TimeZoneResolver(
358 net::URLRequestContextGetter* context,
359 const GURL& url,
360 const ApplyTimeZoneCallback& apply_timezone,
361 const DelayNetworkCallClosure& delay_network_call)
362 : context_(context),
363 url_(url),
364 apply_timezone_(apply_timezone),
365 delay_network_call_(delay_network_call) {
366 DCHECK(!apply_timezone.is_null());
367 }
368
369 TimeZoneResolver::~TimeZoneResolver() {
370 Stop();
371 }
372
373 void TimeZoneResolver::Start() {
374 DCHECK(thread_checker_.CalledOnValidThread());
375 if (!implementation_) {
376 implementation_.reset(new TimeZoneResolverImpl(
377 context_, url_, apply_timezone_, delay_network_call_));
378 implementation_->Start();
379 }
380 }
381
382 void TimeZoneResolver::Stop() {
383 DCHECK(thread_checker_.CalledOnValidThread());
384 implementation_.reset();
385 }
386
387 // static
388 int TimeZoneResolver::MaxRequestsCountForIntervalForTesting(
389 const double interval_seconds) {
390 return MaxRequestsCountForInterval(interval_seconds);
391 }
392
393 // static
394 int TimeZoneResolver::IntervalForNextRequestForTesting(const int requests) {
395 return IntervalForNextRequest(requests);
396 }
397
398 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698