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

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
« no previous file with comments | « chromeos/timezone/timezone_resolver.h ('k') | chromeos/timezone/timezone_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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/prefs/pref_registry_simple.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/rand_util.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/time/time.h"
22 #include "chromeos/geolocation/geoposition.h"
23 #include "chromeos/geolocation/simple_geolocation_provider.h"
24 #include "chromeos/timezone/timezone_provider.h"
25 #include "net/url_request/url_request_context_getter.h"
26 #include "url/gurl.h"
27
28 namespace chromeos {
29
30 namespace {
31
32 class TZRequest;
33
34 // Total timezone resolving process timeout.
35 const unsigned int kRefreshTimeZoneTimeoutSeconds = 60;
36
37 // Initial delay (for the first request).
38 const double kInitialRefreshIntervalSec = 3;
stevenjb 2015/01/22 19:04:30 nit: 3.0
Alexander Alekseev 2015/01/27 19:02:00 Done.
39
40 // Timezone refresh happens at least once each this interval.
41 const double kMaximumRefreshIntervalSec = 6 * 3600; // 6 hours
stevenjb 2015/01/22 19:04:30 nit: 6.0
Alexander Alekseev 2015/01/27 19:02:00 Done.
42
43 // Delay between refresh attempts depends on current number of requests and
44 // this constant.
45 // [interval = kInitialRefreshIntervalMS * (2 ^
46 // (kRefreshIntervalRequestsCountMultiplier * requests_count))]
47 // in seconds.
48 // request_number interval (seconds)
49 // 1 3 (initial, requests_count = 0)
50 // 2 24 (requests_count = 1)
51 // 3 1536 (requests_count = 2)
52 // 4 12288 (requests_count = 3)
53 // 5+ 21600 (maximum)
54 const unsigned int kRefreshIntervalRequestsCountMultiplier = 3;
55
56 // We should limit request rate on browser start to prevent server overload
57 // on permanent browser crash.
58 // If too little time has passed since previous request, initialize
59 // |requests_count_| with |kRefreshTimeZoneInitialRequestCountOnRateLimit|.
60 const double kRefreshTimeZoneMinimumDelayOnRestartSec = 600; // 10 minutes
stevenjb 2015/01/22 19:04:30 10 * 60.0
Alexander Alekseev 2015/01/27 19:02:00 Done.
61 const unsigned int kRefreshTimeZoneInitialRequestCountOnRateLimit = 2;
62
63 int MaxRequestsCountForInterval(const double interval_seconds) {
64 const base::TimeDelta initial_interval =
65 base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec);
66 return log2(interval_seconds / initial_interval.InSecondsF()) /
67 kRefreshIntervalRequestsCountMultiplier;
stevenjb 2015/01/22 19:04:30 I still don't understand why a TimeDelta is being
Alexander Alekseev 2015/01/27 19:02:00 Done.
68 }
69
70 int IntervalForNextRequest(const int requests) {
71 const base::TimeDelta initial_interval =
72 base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec);
73 return static_cast<int>(initial_interval.InSecondsF() *
74 (2 << (static_cast<unsigned>(requests) *
75 kRefreshIntervalRequestsCountMultiplier)));
stevenjb 2015/01/22 19:04:30 same here.
Alexander Alekseev 2015/01/27 19:02:00 Done.
76 }
77
78 } // anonymous namespace
79
80 const char TimeZoneResolver::kLastTimeZoneRefreshTime[] =
81 "timezone_resolver.last_update_time";
82
83 // This class periodically refreshes location and timezone.
84 // It should be destroyed to stop refresh.
85 class TimeZoneResolver::TimeZoneResolverImpl : public base::PowerObserver {
86 public:
87 TimeZoneResolverImpl(
88 net::URLRequestContextGetter* url_context_getter,
89 const GURL& url,
90 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
91 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call,
92 PrefService* local_state);
93
94 ~TimeZoneResolverImpl() override;
95
96 // This is called once after the object is created.
97 void Start();
98
99 // PowerObserver implementation.
100 void OnResume() override;
101
102 // (Re)Starts timer.
103 void ScheduleRequest();
104
105 // Creates new TZRequest.
106 void CreateNewRequest();
107
108 // Called by TZRequest.
109 SimpleGeolocationProvider* geolocation_provider() {
110 return &geolocation_provider_;
111 }
112 TimeZoneProvider* timezone_provider() { return &timezone_provider_; }
113
114 // Update requests count and last request time.
115 void RecordAttempt();
116
117 // This is called by TZRequest. Destroys active request and starts a new one.
118 void RequestIsFinished();
119
120 void ApplyTimeZone(const TimeZoneResponseData* timezone);
121
122 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
123 return delay_network_call_;
124 }
125
126 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> AsWeakPtr();
127
128 private:
129 // Returns delay to next timezone update request
130 base::TimeDelta CalculateNextInterval();
131
132 net::URLRequestContextGetter* url_context_getter_;
133 const GURL url_;
134
135 const TimeZoneResolver::ApplyTimeZoneCallback apply_timezone_;
136 const TimeZoneResolver::DelayNetworkCallClosure delay_network_call_;
137
138 SimpleGeolocationProvider geolocation_provider_;
139 TimeZoneProvider timezone_provider_;
140
141 base::OneShotTimer<TimeZoneResolver::TimeZoneResolverImpl> refresh_timer_;
142
143 // Total number of request attempts.
144 int requests_count_;
145
146 // This is not NULL when update is in progress.
147 scoped_ptr<TZRequest> request_;
148
149 PrefService* local_state_;
150
151 base::WeakPtrFactory<TimeZoneResolver::TimeZoneResolverImpl>
152 weak_ptr_factory_;
153
154 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
155 };
156
157 namespace {
158
159 // This class implements a single timezone refresh attempt.
160 class TZRequest {
161 public:
162 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
163 : resolver_(resolver), weak_ptr_factory_(this) {}
164
165 ~TZRequest();
166
167 // Starts request after specified delay.
168 void Start();
169
170 // Called from SimpleGeolocationProvider when location is resolved.
171 void OnLocationResolved(const Geoposition& position,
172 bool server_error,
173 const base::TimeDelta elapsed);
174
175 // TimeZoneRequest::TimeZoneResponseCallback implementation.
176 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
177 bool server_error);
178
179 base::WeakPtr<TZRequest> AsWeakPtr();
180
181 private:
182 // This is called by network detector when network is available.
183 void StartRequestOnNetworkAvailable();
184
185 TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
186
187 base::WeakPtrFactory<TZRequest> weak_ptr_factory_;
188
189 DISALLOW_COPY_AND_ASSIGN(TZRequest);
190 };
191
192 TZRequest::~TZRequest() {
193 }
194
195 void TZRequest::StartRequestOnNetworkAvailable() {
196 resolver_->RecordAttempt();
197 resolver_->geolocation_provider()->RequestGeolocation(
198 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds),
199 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr()));
200 }
201
202 void TZRequest::Start() {
203 // call to chromeos::DelayNetworkCall
204 resolver_->delay_network_call().Run(
205 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr()));
206 }
207
208 void TZRequest::OnLocationResolved(const Geoposition& position,
209 bool server_error,
210 const base::TimeDelta elapsed) {
211 base::ScopedClosureRunner on_request_finished(
212 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
213 base::Unretained(resolver_)));
214
215 // Ignore invalid position.
216 if (!position.Valid())
217 return;
218
219 const base::TimeDelta timeout =
220 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds);
221
222 if (elapsed >= timeout) {
223 VLOG(1) << "Refresh TimeZone: got location after timeout ("
224 << elapsed.InSecondsF() << " seconds elapsed). Ignored.";
225 return;
226 }
227
228 resolver_->timezone_provider()->RequestTimezone(
229 position,
230 false, // sensor
231 timeout - elapsed,
232 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr()));
233
234 // Prevent |on_request_finished| from firing here.
235 base::Closure unused = on_request_finished.Release();
236 }
237
238 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
239 bool server_error) {
240 base::ScopedClosureRunner on_request_finished(
241 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
242 base::Unretained(resolver_)));
243
244 DCHECK(timezone);
245 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug()
246 << "}.";
247
248 if (timezone->status != TimeZoneResponseData::OK) {
249 VLOG(1) << "Refresh TimeZone: failed to resolve timezone.";
250 return;
251 }
252
253 resolver_->ApplyTimeZone(timezone.get());
254 }
255
256 base::WeakPtr<TZRequest> TZRequest::AsWeakPtr() {
257 return weak_ptr_factory_.GetWeakPtr();
258 }
259
260 } // anonymous namespace
261
262 // ------------------------------------------------------------------------
263 // TimeZoneResolver::TimeZoneResolverImpl implementation.
264
265 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl(
266 net::URLRequestContextGetter* url_context_getter,
267 const GURL& url,
268 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
269 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call,
270 PrefService* local_state)
271 : url_context_getter_(url_context_getter),
272 url_(url),
273 apply_timezone_(apply_timezone),
274 delay_network_call_(delay_network_call),
275 geolocation_provider_(
276 url_context_getter,
277 SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
278 timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()),
279 requests_count_(0),
280 local_state_(local_state),
281 weak_ptr_factory_(this) {
282 DCHECK(!apply_timezone.is_null());
283 DCHECK(!delay_network_call.is_null());
284
285 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
286 power_monitor->AddObserver(this);
287
288 const int64 last_refresh_at_raw =
289 local_state_->GetInt64(kLastTimeZoneRefreshTime);
290 const base::Time last_refresh_at =
291 base::Time::FromInternalValue(last_refresh_at_raw);
292 const base::Time next_refresh_not_before =
293 last_refresh_at +
294 base::TimeDelta::FromSecondsD(kRefreshTimeZoneMinimumDelayOnRestartSec);
295 if (next_refresh_not_before > base::Time::Now()) {
296 requests_count_ = kRefreshTimeZoneInitialRequestCountOnRateLimit;
297 VLOG(1) << "TimeZoneResolverImpl(): initialize requests_count_="
298 << requests_count_ << " because of rate limit.";
299 }
300 }
301
302 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
303 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
304 if (power_monitor)
305 power_monitor->RemoveObserver(this);
306 }
307
308 void TimeZoneResolver::TimeZoneResolverImpl::Start() {
309 // Start() is usually called twice:
310 // - On device boot.
311 // - On user session start.
312 if (request_ || refresh_timer_.IsRunning())
313 return;
314
315 ScheduleRequest();
316 }
317
318 // Returns delay to next timezone update request
319 base::TimeDelta
320 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() {
321 // This is initial request, which should be served immediately.
322 if (requests_count_ == 0) {
323 return base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec);
324 }
325
326 // See comment to kRefreshIntervalRequestsCountMultiplier.
327 if (requests_count_ >=
328 MaxRequestsCountForInterval(kMaximumRefreshIntervalSec)) {
329 return base::TimeDelta::FromSecondsD(kMaximumRefreshIntervalSec);
330 }
331
332 const int base_interval = IntervalForNextRequest(requests_count_);
333 DCHECK_LE(base_interval, kMaximumRefreshIntervalSec);
334
335 // Add jitter to level request rate.
336 const base::TimeDelta interval(
337 base::TimeDelta::FromSecondsD(base::RandDouble() * 2 * base_interval));
338 VLOG(1) << "TimeZoneResolverImpl::CalculateNextInterval(): interval="
339 << interval.InSecondsF();
340 return interval;
341 }
342
343 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
344 requests_count_ = 0;
345 // Refresh timezone immediately.
346 request_.reset();
347 ScheduleRequest();
348 }
349
350 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
351 if (request_)
352 return;
353
354 // base::OneShotTimer
355 base::TimeDelta interval = CalculateNextInterval();
356 refresh_timer_.Stop();
357 refresh_timer_.Start(
358 FROM_HERE, interval,
359 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest,
360 AsWeakPtr()));
361 }
362
363 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() {
364 if (request_)
365 return;
366
367 refresh_timer_.Stop();
368
369 request_.reset(new TZRequest(this));
370 request_->Start();
371 }
372
373 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
374 local_state_->SetInt64(kLastTimeZoneRefreshTime,
375 base::Time::Now().ToInternalValue());
376 ++requests_count_;
377 }
378
379 void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() {
380 request_.reset();
381 ScheduleRequest();
382 }
383
384 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
385 const TimeZoneResponseData* timezone) {
386 apply_timezone_.Run(timezone);
387 }
388
389 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl>
390 TimeZoneResolver::TimeZoneResolverImpl::AsWeakPtr() {
391 return weak_ptr_factory_.GetWeakPtr();
392 }
393
394 // ------------------------------------------------------------------------
395 // TimeZoneResolver implementation
396
397 TimeZoneResolver::TimeZoneResolver(
398 net::URLRequestContextGetter* context,
399 const GURL& url,
400 const ApplyTimeZoneCallback& apply_timezone,
401 const DelayNetworkCallClosure& delay_network_call,
402 PrefService* local_state)
403 : context_(context),
404 url_(url),
405 apply_timezone_(apply_timezone),
406 delay_network_call_(delay_network_call),
407 local_state_(local_state) {
408 DCHECK(!apply_timezone.is_null());
409 }
410
411 TimeZoneResolver::~TimeZoneResolver() {
412 Stop();
413 }
414
415 void TimeZoneResolver::Start() {
416 DCHECK(thread_checker_.CalledOnValidThread());
417 if (!implementation_) {
418 implementation_.reset(new TimeZoneResolverImpl(
419 context_, url_, apply_timezone_, delay_network_call_, local_state_));
420 implementation_->Start();
421 }
422 }
423
424 void TimeZoneResolver::Stop() {
425 DCHECK(thread_checker_.CalledOnValidThread());
426 implementation_.reset();
427 }
428
429 // static
430 int TimeZoneResolver::MaxRequestsCountForIntervalForTesting(
431 const double interval_seconds) {
432 return MaxRequestsCountForInterval(interval_seconds);
433 }
434
435 // static
436 int TimeZoneResolver::IntervalForNextRequestForTesting(const int requests) {
437 return IntervalForNextRequest(requests);
438 }
439
440 // static
441 void TimeZoneResolver::RegisterPrefs(PrefRegistrySimple* registry) {
442 registry->RegisterInt64Pref(kLastTimeZoneRefreshTime, 0);
443 }
444
445 } // namespace chromeos
OLDNEW
« no previous file with comments | « chromeos/timezone/timezone_resolver.h ('k') | chromeos/timezone/timezone_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698