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

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