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

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

Powered by Google App Engine
This is Rietveld 408576698