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/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; | |
Dmitry Polukhin
2015/01/20 15:32:28
Please add jitter and also consider not sending re
Alexander Alekseev
2015/01/22 18:55:23
Done.
| |
36 | |
37 // Timezone refresh happens at least once each this interval. | |
38 const int64 kMaximalRefreshIntervalMS = 6 * 3600 * 1000; // 6 hours | |
stevenjb
2015/01/20 18:30:42
Maximal -> Maximum
Alexander Alekseev
2015/01/22 18:55:22
Done.
| |
39 | |
40 // Delay between refresh attempts depends on current number of requests and | |
41 // this constant. | |
42 // [interval = kInitialRefreshIntervalMS * (2 ^ | |
43 // (kRefreshIntervalRequestsCountMultiplier * requests_count))] | |
44 // in seconds. | |
45 const unsigned long kRefreshIntervalRequestsCountMultiplier = 3; | |
46 | |
47 int MaxRequestsCountForInterval(const double interval_seconds) { | |
48 const base::TimeDelta initial_interval = | |
49 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS); | |
stevenjb
2015/01/20 18:30:42
Am I missing something or is this just being used
Alexander Alekseev
2015/01/22 18:55:22
Done.
| |
50 return log2(interval_seconds / initial_interval.InSecondsF()) / | |
51 kRefreshIntervalRequestsCountMultiplier; | |
52 } | |
53 | |
54 int IntervalForNextRequest(const int requests) { | |
55 const base::TimeDelta initial_interval = | |
56 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS); | |
stevenjb
2015/01/20 18:30:42
Same comment here. Do we ever use kInitialRefreshI
Alexander Alekseev
2015/01/22 18:55:23
Done.
| |
57 return static_cast<int>(initial_interval.InSecondsF() * | |
58 (2 << (static_cast<unsigned>(requests) * | |
59 kRefreshIntervalRequestsCountMultiplier))); | |
60 } | |
61 | |
62 } // anonymous namespace | |
63 | |
64 // This class periodically refreshes location and timezone. | |
65 // It should be destroyed to stop refresh. | |
66 class TimeZoneResolver::TimeZoneResolverImpl : public base::PowerObserver { | |
67 public: | |
68 TimeZoneResolverImpl( | |
69 net::URLRequestContextGetter* url_context_getter, | |
70 const GURL& url, | |
71 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone, | |
72 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call); | |
73 | |
74 ~TimeZoneResolverImpl() override; | |
75 | |
76 // This is called once after the object is created. | |
77 void Start(); | |
78 | |
79 // PowerObserver implementation. | |
80 void OnResume() override; | |
81 | |
82 // (Re)Starts timer. | |
83 void ScheduleRequest(); | |
84 | |
85 // Creates new TZRequest. | |
86 void CreateNewRequest(); | |
87 | |
88 // Called by TZRequest. | |
89 SimpleGeolocationProvider* geolocation_provider() { | |
90 return &geolocation_provider_; | |
91 } | |
92 TimeZoneProvider* timezone_provider() { return &timezone_provider_; } | |
93 | |
94 // Update requests count and last request time. | |
95 void RecordAttempt(); | |
96 | |
97 // This is called by TZRequest. Destroys active request and starts a new one. | |
98 void RequestIsFinished(); | |
99 | |
100 void ApplyTimeZone(const TimeZoneResponseData* timezone); | |
101 | |
102 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const { | |
103 return delay_network_call_; | |
104 } | |
105 | |
106 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> AsWeakPtr(); | |
107 | |
108 private: | |
109 // Returns delay to next timezone update request | |
110 base::TimeDelta CalculateNextInterval(); | |
111 | |
112 net::URLRequestContextGetter* url_context_getter_; | |
113 const GURL url_; | |
114 | |
115 const TimeZoneResolver::ApplyTimeZoneCallback apply_timezone_; | |
116 const TimeZoneResolver::DelayNetworkCallClosure delay_network_call_; | |
117 | |
118 SimpleGeolocationProvider geolocation_provider_; | |
119 TimeZoneProvider timezone_provider_; | |
120 | |
121 base::OneShotTimer<TimeZoneResolver::TimeZoneResolverImpl> refresh_timer_; | |
122 | |
123 // Total number of request attempts. | |
124 int requests_count_; | |
125 | |
126 // This is not NULL when update is in progress. | |
127 scoped_ptr<TZRequest> request_; | |
128 | |
129 base::WeakPtrFactory<TimeZoneResolver::TimeZoneResolverImpl> | |
130 weak_ptr_factory_; | |
131 | |
132 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl); | |
133 }; | |
134 | |
135 namespace { | |
136 | |
137 // This class implements a single timezone refresh attempt. | |
138 class TZRequest { | |
139 public: | |
140 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver) | |
141 : resolver_(resolver), weak_ptr_factory_(this) {} | |
142 | |
143 ~TZRequest(); | |
144 | |
145 // Starts request after specified delay. | |
146 void Start(); | |
147 | |
148 // Called from SimpleGeolocationProvider when location is resolved. | |
149 void OnLocationResolved(const Geoposition& position, | |
150 bool server_error, | |
151 const base::TimeDelta elapsed); | |
152 | |
153 // TimeZoneRequest::TimeZoneResponseCallback implementation. | |
154 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone, | |
155 bool server_error); | |
156 | |
157 base::WeakPtr<TZRequest> AsWeakPtr(); | |
158 | |
159 private: | |
160 // This is called by network detector when network is available. | |
161 void StartRequestOnNetworkAvailable(); | |
162 | |
163 TimeZoneResolver::TimeZoneResolverImpl* const resolver_; | |
164 | |
165 base::WeakPtrFactory<TZRequest> weak_ptr_factory_; | |
166 | |
167 DISALLOW_COPY_AND_ASSIGN(TZRequest); | |
168 }; | |
169 | |
170 TZRequest::~TZRequest() { | |
171 } | |
172 | |
173 void TZRequest::StartRequestOnNetworkAvailable() { | |
174 resolver_->RecordAttempt(); | |
175 resolver_->geolocation_provider()->RequestGeolocation( | |
Dmitry Polukhin
2015/01/20 15:32:28
Please make sure that geolocation_provider does ha
Alexander Alekseev
2015/01/22 18:55:23
It has it. It makes (probably) several requests un
| |
176 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds), | |
177 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr())); | |
178 } | |
179 | |
180 void TZRequest::Start() { | |
181 // call to chromeos::DelayNetworkCall | |
182 resolver_->delay_network_call().Run( | |
183 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr())); | |
184 } | |
185 | |
186 void TZRequest::OnLocationResolved(const Geoposition& position, | |
187 bool server_error, | |
188 const base::TimeDelta elapsed) { | |
189 base::ScopedClosureRunner on_request_finished( | |
190 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished, | |
191 base::Unretained(resolver_))); | |
192 | |
193 // Ignore invalid position. | |
194 if (!position.Valid()) | |
195 return; | |
196 | |
197 const base::TimeDelta timeout = | |
198 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds); | |
199 | |
200 if (elapsed >= timeout) { | |
201 VLOG(1) << "Refresh TimeZone: got location after timeout (" | |
202 << elapsed.InSecondsF() << " seconds elapsed). Ignored."; | |
203 return; | |
204 } | |
205 | |
206 resolver_->timezone_provider()->RequestTimezone( | |
207 position, | |
208 false, // sensor | |
209 timeout - elapsed, | |
210 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr())); | |
211 | |
212 // Prevent |on_request_finished| from firing here. | |
213 base::Closure unused = on_request_finished.Release(); | |
214 } | |
215 | |
216 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone, | |
217 bool server_error) { | |
218 base::ScopedClosureRunner on_request_finished( | |
219 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished, | |
220 base::Unretained(resolver_))); | |
221 | |
222 DCHECK(timezone); | |
223 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug() | |
224 << "}."; | |
225 | |
226 if (timezone->status != TimeZoneResponseData::OK) { | |
227 VLOG(1) << "Refresh TimeZone: failed to resolve timezone."; | |
228 return; | |
229 } | |
230 | |
231 resolver_->ApplyTimeZone(timezone.get()); | |
232 } | |
233 | |
234 base::WeakPtr<TZRequest> TZRequest::AsWeakPtr() { | |
235 return weak_ptr_factory_.GetWeakPtr(); | |
236 } | |
237 | |
238 } // anonymous namespace | |
239 | |
240 // ------------------------------------------------------------------------ | |
241 // TimeZoneResolver::TimeZoneResolverImpl implementation. | |
242 | |
243 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl( | |
244 net::URLRequestContextGetter* url_context_getter, | |
245 const GURL& url, | |
246 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone, | |
247 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call) | |
248 : url_context_getter_(url_context_getter), | |
249 url_(url), | |
250 apply_timezone_(apply_timezone), | |
251 delay_network_call_(delay_network_call), | |
252 geolocation_provider_( | |
253 url_context_getter, | |
254 SimpleGeolocationProvider::DefaultGeolocationProviderURL()), | |
255 timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()), | |
256 requests_count_(0), | |
257 weak_ptr_factory_(this) { | |
258 DCHECK(!apply_timezone.is_null()); | |
259 DCHECK(!delay_network_call.is_null()); | |
260 | |
261 base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); | |
262 power_monitor->AddObserver(this); | |
263 } | |
264 | |
265 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() { | |
266 base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); | |
267 if (power_monitor) | |
268 power_monitor->RemoveObserver(this); | |
269 } | |
270 | |
271 void TimeZoneResolver::TimeZoneResolverImpl::Start() { | |
272 // Start() is usually called twice: | |
273 // - On device boot. | |
274 // - On user session start. | |
275 if (request_ || refresh_timer_.IsRunning()) | |
276 return; | |
277 | |
278 ScheduleRequest(); | |
279 } | |
280 | |
281 // Returns delay to next timezone update request | |
282 base::TimeDelta | |
283 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() { | |
284 const base::TimeDelta initial_interval = | |
285 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS); | |
286 const base::TimeDelta maximum_interval = | |
287 base::TimeDelta::FromMilliseconds(kMaximalRefreshIntervalMS); | |
288 | |
289 // This is initial request, which should be served immediately. | |
290 if (requests_count_ == 0) | |
291 return initial_interval; | |
292 | |
293 // See comment to kRefreshIntervalRequestsCountMultiplier. | |
294 if (requests_count_ >= | |
295 MaxRequestsCountForInterval(maximum_interval.InSecondsF())) { | |
296 return maximum_interval; | |
297 } | |
298 | |
299 const base::TimeDelta interval( | |
300 base::TimeDelta::FromSeconds(IntervalForNextRequest(requests_count_))); | |
301 DCHECK_LE(interval, maximum_interval); | |
302 return interval; | |
303 } | |
304 | |
305 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() { | |
306 requests_count_ = 0; | |
307 // Refresh timezone immediately. | |
308 request_.reset(); | |
309 ScheduleRequest(); | |
310 } | |
311 | |
312 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() { | |
313 if (request_) | |
314 return; | |
315 | |
316 // base::OneShotTimer | |
317 base::TimeDelta interval = CalculateNextInterval(); | |
318 refresh_timer_.Stop(); | |
319 refresh_timer_.Start( | |
320 FROM_HERE, interval, | |
321 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest, | |
322 AsWeakPtr())); | |
323 } | |
324 | |
325 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() { | |
326 if (request_) | |
327 return; | |
328 | |
329 refresh_timer_.Stop(); | |
330 | |
331 request_.reset(new TZRequest(this)); | |
332 request_->Start(); | |
333 } | |
334 | |
335 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() { | |
336 ++requests_count_; | |
337 } | |
338 | |
339 void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() { | |
340 request_.reset(); | |
341 ScheduleRequest(); | |
342 } | |
343 | |
344 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone( | |
345 const TimeZoneResponseData* timezone) { | |
346 apply_timezone_.Run(timezone); | |
347 } | |
348 | |
349 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> | |
350 TimeZoneResolver::TimeZoneResolverImpl::AsWeakPtr() { | |
351 return weak_ptr_factory_.GetWeakPtr(); | |
352 } | |
353 | |
354 // ------------------------------------------------------------------------ | |
355 // TimeZoneResolver implementation | |
356 | |
357 TimeZoneResolver::TimeZoneResolver( | |
358 net::URLRequestContextGetter* context, | |
359 const GURL& url, | |
360 const ApplyTimeZoneCallback& apply_timezone, | |
361 const DelayNetworkCallClosure& delay_network_call) | |
362 : context_(context), | |
363 url_(url), | |
364 apply_timezone_(apply_timezone), | |
365 delay_network_call_(delay_network_call) { | |
366 DCHECK(!apply_timezone.is_null()); | |
367 } | |
368 | |
369 TimeZoneResolver::~TimeZoneResolver() { | |
370 Stop(); | |
371 } | |
372 | |
373 void TimeZoneResolver::Start() { | |
374 DCHECK(thread_checker_.CalledOnValidThread()); | |
375 if (!implementation_) { | |
376 implementation_.reset(new TimeZoneResolverImpl( | |
377 context_, url_, apply_timezone_, delay_network_call_)); | |
378 implementation_->Start(); | |
379 } | |
380 } | |
381 | |
382 void TimeZoneResolver::Stop() { | |
383 DCHECK(thread_checker_.CalledOnValidThread()); | |
384 implementation_.reset(); | |
385 } | |
386 | |
387 // static | |
388 int TimeZoneResolver::MaxRequestsCountForIntervalForTesting( | |
389 const double interval_seconds) { | |
390 return MaxRequestsCountForInterval(interval_seconds); | |
391 } | |
392 | |
393 // static | |
394 int TimeZoneResolver::IntervalForNextRequestForTesting(const int requests) { | |
395 return IntervalForNextRequest(requests); | |
396 } | |
397 | |
398 } // namespace chromeos | |
OLD | NEW |