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

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: Comment updated. 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 each this interval.
stevenjb 2015/01/05 17:34:21 'at least once each interval'
Alexander Alekseev 2015/01/15 18:59:03 Done.
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. If [kRefreshIntervalBasePower=3], (base is always 2),
42 // then [base_multiplier = (2 << kRefreshIntervalBasePower) = 16], and
43 // this gives [interval = initial_interval * (base_multiplier << requests_count)
44 // = 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
45 const unsigned long kRefreshIntervalBasePower = 3;
46
47 } // anonymous namespace
48
49 // This class periodically refreshes location and timezone.
50 // It should be destroyed to stop refresh.
51 class TimeZoneResolver::TimeZoneResolverImpl
52 : public base::PowerObserver,
53 public base::SupportsWeakPtr<TimeZoneResolver::TimeZoneResolverImpl> {
54 public:
55 TimeZoneResolverImpl(
56 net::URLRequestContextGetter* url_context_getter,
57 const GURL& url,
58 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
59 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call);
60
61 ~TimeZoneResolverImpl() override;
62
63 // This is called once after the object is created.
64 void Start();
65
66 // PowerObserver implementation.
67 void OnResume() override;
68
69 // (Re)Starts timer.
70 void ScheduleRequest();
71
72 // Creates new TZRequest.
73 void CreateNewRequest();
74
75 // Called by TZRequest.
76 SimpleGeolocationProvider* geolocation_provider() {
77 return &geolocation_provider_;
78 }
79 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
80
81 // Update requests count and last request time.
82 void RecordAttempt();
83
84 TZRequest* ReleaseRequest();
85
86 void ApplyTimeZone(const TimeZoneResponseData* timezone);
87
88 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
89 return delay_network_call_;
90 }
91
92 private:
93 // Returns delay to next timezone update request
94 base::TimeDelta CalculateNextInterval();
95
96 net::URLRequestContextGetter* url_context_getter_;
97 const GURL url_;
98
99 const TimeZoneResolver::ApplyTimeZoneCallback apply_timezone_;
100 const TimeZoneResolver::DelayNetworkCallClosure delay_network_call_;
101
102 SimpleGeolocationProvider geolocation_provider_;
103 TimeZoneProvider timezone_provider_;
104
105 base::OneShotTimer<TimeZoneResolver::TimeZoneResolverImpl> refresh_timer_;
106
107 // Total number of requst attempts.
Bernhard Bauer 2015/01/05 10:22:33 Nit: "request"
Alexander Alekseev 2015/01/15 18:59:03 Done.
108 unsigned int requests_count_;
109
110 // This is not NULL when update is in progress.
111 scoped_ptr<TZRequest> request_;
112
113 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
114 };
115
116 namespace {
117
118 // This class implements a single timezone refresh attempt.
119 class TZRequest : public base::SupportsWeakPtr<TZRequest> {
120 public:
121 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
122 : resolver_(resolver) {}
123
124 ~TZRequest();
125
126 // Starts request after specified delay.
127 void Start();
128
129 // Called from SimpleGeolocationProvider when location is resolved.
130 void OnLocationResolved(const Geoposition& position,
131 bool server_error,
132 const base::TimeDelta elapsed);
133
134 // TimeZoneRequest::TimeZoneResponseCallback implementation.
135 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
136 bool server_error);
137
138 private:
139 // Schedules new request and destroys current object.
140 void RequestIsFinished();
141
142 // This is called by network detector when network is available.
143 void StartRequestOnNetworkAvailable();
144
145 TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
146
147 DISALLOW_COPY_AND_ASSIGN(TZRequest);
148 };
149
150 TZRequest::~TZRequest() {
151 }
152
153 void TZRequest::StartRequestOnNetworkAvailable() {
154 resolver_->RecordAttempt();
155 resolver_->geolocation_provider()->RequestGeolocation(
156 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds),
157 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr()));
158 }
159
160 void TZRequest::Start() {
161 // call to chromeos::DelayNetworkCall
162 resolver_->delay_network_call().Run(
163 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr()));
164 }
165
166 void TZRequest::OnLocationResolved(const Geoposition& position,
167 bool server_error,
168 const base::TimeDelta elapsed) {
169 base::ScopedClosureRunner on_request_finished(
170 base::Bind(&TZRequest::RequestIsFinished, base::Unretained(this)));
171
172 // Ignore invalid position.
173 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.
174 return;
175 }
176
177 const base::TimeDelta timeout =
178 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds);
179
180 if (elapsed >= timeout) {
181 VLOG(1) << "Refresh TimeZone: got location after timeout ("
182 << elapsed.InSecondsF() << " seconds elapsed). Ignored.";
183 return;
184 }
185
186 resolver_->timezone_provider()->RequestTimezone(
187 position,
188 false, // sensor
189 timeout - elapsed,
190 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr()));
191
192 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.
193 }
194
195 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
196 bool server_error) {
197 base::ScopedClosureRunner on_request_finished(
198 base::Bind(&TZRequest::RequestIsFinished, base::Unretained(this)));
199
200 DCHECK(timezone.get());
201 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug()
202 << "}.";
203
204 if (timezone->status != TimeZoneResponseData::OK) {
205 VLOG(1) << "Refresh TimeZone: failed to resolve timezone.";
206 return;
207 }
208
209 resolver_->ApplyTimeZone(timezone.get());
210 }
211
212 void TZRequest::RequestIsFinished() {
213 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.
214 resolver_->ScheduleRequest();
215 // This object is destroyed here.
216 }
217
218 } // anonymous namespace
219
220 // ------------------------------------------------------------------------
221 // TimeZoneResolver::TimeZoneResolverImpl implementation.
222
223 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl(
224 net::URLRequestContextGetter* url_context_getter,
225 const GURL& url,
226 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
227 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call)
228 : url_context_getter_(url_context_getter),
229 url_(url),
230 apply_timezone_(apply_timezone),
231 delay_network_call_(delay_network_call),
232 geolocation_provider_(
233 url_context_getter,
234 SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
235 timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()),
236 requests_count_(0) {
237 DCHECK(!apply_timezone.is_null());
238 DCHECK(!delay_network_call.is_null());
239
240 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
241 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.
242 power_monitor->AddObserver(this);
243 }
244
245 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
246 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
247 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.
248 power_monitor->RemoveObserver(this);
249 }
250
251 void TimeZoneResolver::TimeZoneResolverImpl::Start() {
252 // Start() is usually called twice:
253 // - On device boot.
254 // - On user session start.
255 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.
256 return;
257
258 ScheduleRequest();
259 }
260
261 // Returns delay to next timezone update request
262 base::TimeDelta
263 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() {
264 const base::TimeDelta initial_interval =
265 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS);
266 const base::TimeDelta maximum_interval =
267 base::TimeDelta::FromMilliseconds(kMaximalRefreshIntervalMS);
268
269 // 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.
270 if (requests_count_ == 0)
271 return initial_interval;
272
273 // See comment to kRefreshIntervalBasePower.
274 const unsigned long power = kRefreshIntervalBasePower;
275 const unsigned long base_multiplier = 2 << power;
276
277 if (requests_count_ >
278 ::log2(maximum_interval.InSecondsF() / initial_interval.InSecondsF()) /
279 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.
280 return maximum_interval;
281
282 const base::TimeDelta interval(base::TimeDelta::FromSeconds(
283 initial_interval.InSecondsF() * (base_multiplier << requests_count_)));
284 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.
285 }
286
287 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
288 requests_count_ = 0;
289 // Refresh timezone immediately.
290 request_.reset();
291 ScheduleRequest();
292 }
293
294 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
295 if (request_.get())
296 return;
297
298 // base::OneShotTimer
299 base::TimeDelta interval = CalculateNextInterval();
300 refresh_timer_.Stop();
301 refresh_timer_.Start(
302 FROM_HERE, interval,
303 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest,
304 AsWeakPtr()));
305 }
306
307 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() {
308 if (request_.get())
309 return;
310
311 refresh_timer_.Stop();
312
313 request_.reset(new TZRequest(this));
314 request_->Start();
315 }
316
317 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
318 ++requests_count_;
319 }
320
321 TZRequest* TimeZoneResolver::TimeZoneResolverImpl::ReleaseRequest() {
322 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.
323 }
324
325 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
326 const TimeZoneResponseData* timezone) {
327 apply_timezone_.Run(timezone);
328 }
329
330 // ------------------------------------------------------------------------
331 // TimeZoneResolver implementation
332
333 TimeZoneResolver::TimeZoneResolver(
334 net::URLRequestContextGetter* context,
335 const GURL& url,
336 const ApplyTimeZoneCallback& apply_timezone,
337 const DelayNetworkCallClosure& delay_network_call)
338 : context_(context),
339 url_(url),
340 apply_timezone_(apply_timezone),
341 delay_network_call_(delay_network_call),
342 implementation_(nullptr) {
343 DCHECK(!apply_timezone.is_null());
344 }
345
346 TimeZoneResolver::~TimeZoneResolver() {
347 Stop();
348 }
349
350 void TimeZoneResolver::Start() {
351 DCHECK(thread_checker_.CalledOnValidThread());
352 if (!implementation_) {
353 implementation_ = new TimeZoneResolverImpl(context_, url_, apply_timezone_,
354 delay_network_call_);
355 implementation_->Start();
356 }
357 }
358
359 void TimeZoneResolver::Stop() {
360 DCHECK(thread_checker_.CalledOnValidThread());
361 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.
362 delete implementation_;
363 implementation_ = nullptr;
364 }
365 }
366
367 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698