OLD | NEW |
---|---|
(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 | |
OLD | NEW |