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; | |
36 | |
37 // Timezone refresh happens at least each this interval. | |
stevenjb
2015/01/05 17:34:21
'at least once each interval'
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
38 const int64 kMaximalRefreshIntervalMS = 6 * 3600 * 1000; // 6 hours | |
39 | |
40 // Delay between refresh attempts depends on current number of requests and | |
41 // this constant. If [kRefreshIntervalBasePower=3], (base is always 2), | |
42 // then [base_multiplier = (2 << kRefreshIntervalBasePower) = 16], and | |
43 // this gives [interval = initial_interval * (base_multiplier << requests_count) | |
44 // = initial_interval * (16 << requests_count_)] in seconds. | |
stevenjb
2015/01/05 17:34:21
Is this level of complexity really necessary? Coul
Alexander Alekseev
2015/01/15 18:59:03
(2 ^ requests_count) was my initial implementation
| |
45 const unsigned long kRefreshIntervalBasePower = 3; | |
46 | |
47 } // anonymous namespace | |
48 | |
49 // This class periodically refreshes location and timezone. | |
50 // It should be destroyed to stop refresh. | |
51 class TimeZoneResolver::TimeZoneResolverImpl | |
52 : public base::PowerObserver, | |
53 public base::SupportsWeakPtr<TimeZoneResolver::TimeZoneResolverImpl> { | |
54 public: | |
55 TimeZoneResolverImpl( | |
56 net::URLRequestContextGetter* url_context_getter, | |
57 const GURL& url, | |
58 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone, | |
59 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call); | |
60 | |
61 ~TimeZoneResolverImpl() override; | |
62 | |
63 // This is called once after the object is created. | |
64 void Start(); | |
65 | |
66 // PowerObserver implementation. | |
67 void OnResume() override; | |
68 | |
69 // (Re)Starts timer. | |
70 void ScheduleRequest(); | |
71 | |
72 // Creates new TZRequest. | |
73 void CreateNewRequest(); | |
74 | |
75 // Called by TZRequest. | |
76 SimpleGeolocationProvider* geolocation_provider() { | |
77 return &geolocation_provider_; | |
78 } | |
79 TimeZoneProvider* timezone_provider() { return &timezone_provider_; } | |
stevenjb
2015/01/05 17:34:21
Can either of these be const?
Alexander Alekseev
2015/01/15 18:59:03
It would require |timezone_provider_| to be a scop
stevenjb
2015/01/20 18:30:42
Why would it need to be scoped to be const? If all
| |
80 | |
81 // Update requests count and last request time. | |
82 void RecordAttempt(); | |
83 | |
84 TZRequest* ReleaseRequest(); | |
85 | |
86 void ApplyTimeZone(const TimeZoneResponseData* timezone); | |
87 | |
88 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const { | |
89 return delay_network_call_; | |
90 } | |
91 | |
92 private: | |
93 // Returns delay to next timezone update request | |
94 base::TimeDelta CalculateNextInterval(); | |
95 | |
96 net::URLRequestContextGetter* url_context_getter_; | |
97 const GURL url_; | |
98 | |
99 const TimeZoneResolver::ApplyTimeZoneCallback apply_timezone_; | |
100 const TimeZoneResolver::DelayNetworkCallClosure delay_network_call_; | |
101 | |
102 SimpleGeolocationProvider geolocation_provider_; | |
103 TimeZoneProvider timezone_provider_; | |
104 | |
105 base::OneShotTimer<TimeZoneResolver::TimeZoneResolverImpl> refresh_timer_; | |
106 | |
107 // Total number of requst attempts. | |
Bernhard Bauer
2015/01/05 10:22:33
Nit: "request"
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
108 unsigned int requests_count_; | |
109 | |
110 // This is not NULL when update is in progress. | |
111 scoped_ptr<TZRequest> request_; | |
112 | |
113 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl); | |
114 }; | |
115 | |
116 namespace { | |
117 | |
118 // This class implements a single timezone refresh attempt. | |
119 class TZRequest : public base::SupportsWeakPtr<TZRequest> { | |
120 public: | |
121 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver) | |
122 : resolver_(resolver) {} | |
123 | |
124 ~TZRequest(); | |
125 | |
126 // Starts request after specified delay. | |
127 void Start(); | |
128 | |
129 // Called from SimpleGeolocationProvider when location is resolved. | |
130 void OnLocationResolved(const Geoposition& position, | |
131 bool server_error, | |
132 const base::TimeDelta elapsed); | |
133 | |
134 // TimeZoneRequest::TimeZoneResponseCallback implementation. | |
135 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone, | |
136 bool server_error); | |
137 | |
138 private: | |
139 // Schedules new request and destroys current object. | |
140 void RequestIsFinished(); | |
141 | |
142 // This is called by network detector when network is available. | |
143 void StartRequestOnNetworkAvailable(); | |
144 | |
145 TimeZoneResolver::TimeZoneResolverImpl* const resolver_; | |
146 | |
147 DISALLOW_COPY_AND_ASSIGN(TZRequest); | |
148 }; | |
149 | |
150 TZRequest::~TZRequest() { | |
151 } | |
152 | |
153 void TZRequest::StartRequestOnNetworkAvailable() { | |
154 resolver_->RecordAttempt(); | |
155 resolver_->geolocation_provider()->RequestGeolocation( | |
156 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds), | |
157 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr())); | |
158 } | |
159 | |
160 void TZRequest::Start() { | |
161 // call to chromeos::DelayNetworkCall | |
162 resolver_->delay_network_call().Run( | |
163 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr())); | |
164 } | |
165 | |
166 void TZRequest::OnLocationResolved(const Geoposition& position, | |
167 bool server_error, | |
168 const base::TimeDelta elapsed) { | |
169 base::ScopedClosureRunner on_request_finished( | |
170 base::Bind(&TZRequest::RequestIsFinished, base::Unretained(this))); | |
171 | |
172 // Ignore invalid position. | |
173 if (!position.Valid()) { | |
Bernhard Bauer
2015/01/05 10:22:33
No braces for one-line bodies.
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
174 return; | |
175 } | |
176 | |
177 const base::TimeDelta timeout = | |
178 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds); | |
179 | |
180 if (elapsed >= timeout) { | |
181 VLOG(1) << "Refresh TimeZone: got location after timeout (" | |
182 << elapsed.InSecondsF() << " seconds elapsed). Ignored."; | |
183 return; | |
184 } | |
185 | |
186 resolver_->timezone_provider()->RequestTimezone( | |
187 position, | |
188 false, // sensor | |
189 timeout - elapsed, | |
190 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr())); | |
191 | |
192 base::Closure unused = on_request_finished.Release(); | |
stevenjb
2015/01/05 17:34:21
nit: Add a comment for those who aren't familiar w
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
193 } | |
194 | |
195 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone, | |
196 bool server_error) { | |
197 base::ScopedClosureRunner on_request_finished( | |
198 base::Bind(&TZRequest::RequestIsFinished, base::Unretained(this))); | |
199 | |
200 DCHECK(timezone.get()); | |
201 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug() | |
202 << "}."; | |
203 | |
204 if (timezone->status != TimeZoneResponseData::OK) { | |
205 VLOG(1) << "Refresh TimeZone: failed to resolve timezone."; | |
206 return; | |
207 } | |
208 | |
209 resolver_->ApplyTimeZone(timezone.get()); | |
210 } | |
211 | |
212 void TZRequest::RequestIsFinished() { | |
213 scoped_ptr<TZRequest> self(resolver_->ReleaseRequest()); | |
Bernhard Bauer
2015/01/05 10:22:33
All this method does is call methods on the resolv
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
214 resolver_->ScheduleRequest(); | |
215 // This object is destroyed here. | |
216 } | |
217 | |
218 } // anonymous namespace | |
219 | |
220 // ------------------------------------------------------------------------ | |
221 // TimeZoneResolver::TimeZoneResolverImpl implementation. | |
222 | |
223 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl( | |
224 net::URLRequestContextGetter* url_context_getter, | |
225 const GURL& url, | |
226 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone, | |
227 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call) | |
228 : url_context_getter_(url_context_getter), | |
229 url_(url), | |
230 apply_timezone_(apply_timezone), | |
231 delay_network_call_(delay_network_call), | |
232 geolocation_provider_( | |
233 url_context_getter, | |
234 SimpleGeolocationProvider::DefaultGeolocationProviderURL()), | |
235 timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()), | |
236 requests_count_(0) { | |
237 DCHECK(!apply_timezone.is_null()); | |
238 DCHECK(!delay_network_call.is_null()); | |
239 | |
240 base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); | |
241 DCHECK(power_monitor); | |
Bernhard Bauer
2015/01/05 10:22:33
This DCHECK isn't really necessary; if |power_moni
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
242 power_monitor->AddObserver(this); | |
243 } | |
244 | |
245 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() { | |
246 base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); | |
247 DCHECK(power_monitor); | |
stevenjb
2015/01/05 17:34:22
power_monitor could be NULL on shutdown, so just r
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
248 power_monitor->RemoveObserver(this); | |
249 } | |
250 | |
251 void TimeZoneResolver::TimeZoneResolverImpl::Start() { | |
252 // Start() is usually called twice: | |
253 // - On device boot. | |
254 // - On user session start. | |
255 if (request_.get() || refresh_timer_.IsRunning()) | |
Bernhard Bauer
2015/01/05 10:22:33
scoped_ptr has a bool overload, so you don't need
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
256 return; | |
257 | |
258 ScheduleRequest(); | |
259 } | |
260 | |
261 // Returns delay to next timezone update request | |
262 base::TimeDelta | |
263 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() { | |
264 const base::TimeDelta initial_interval = | |
265 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS); | |
266 const base::TimeDelta maximum_interval = | |
267 base::TimeDelta::FromMilliseconds(kMaximalRefreshIntervalMS); | |
268 | |
269 // This is initial request, which should be served immediate. | |
Bernhard Bauer
2015/01/05 10:22:33
Nit: "immediately"
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
270 if (requests_count_ == 0) | |
271 return initial_interval; | |
272 | |
273 // See comment to kRefreshIntervalBasePower. | |
274 const unsigned long power = kRefreshIntervalBasePower; | |
275 const unsigned long base_multiplier = 2 << power; | |
276 | |
277 if (requests_count_ > | |
278 ::log2(maximum_interval.InSecondsF() / initial_interval.InSecondsF()) / | |
279 power) | |
Bernhard Bauer
2015/01/05 10:22:33
Wait... You divide by |power| here. That means you
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
280 return maximum_interval; | |
281 | |
282 const base::TimeDelta interval(base::TimeDelta::FromSeconds( | |
283 initial_interval.InSecondsF() * (base_multiplier << requests_count_))); | |
284 return std::min(interval, maximum_interval); | |
Bernhard Bauer
2015/01/05 10:22:33
Assuming you clamp at |maximum_interval| already a
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
285 } | |
286 | |
287 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() { | |
288 requests_count_ = 0; | |
289 // Refresh timezone immediately. | |
290 request_.reset(); | |
291 ScheduleRequest(); | |
292 } | |
293 | |
294 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() { | |
295 if (request_.get()) | |
296 return; | |
297 | |
298 // base::OneShotTimer | |
299 base::TimeDelta interval = CalculateNextInterval(); | |
300 refresh_timer_.Stop(); | |
301 refresh_timer_.Start( | |
302 FROM_HERE, interval, | |
303 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest, | |
304 AsWeakPtr())); | |
305 } | |
306 | |
307 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() { | |
308 if (request_.get()) | |
309 return; | |
310 | |
311 refresh_timer_.Stop(); | |
312 | |
313 request_.reset(new TZRequest(this)); | |
314 request_->Start(); | |
315 } | |
316 | |
317 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() { | |
318 ++requests_count_; | |
319 } | |
320 | |
321 TZRequest* TimeZoneResolver::TimeZoneResolverImpl::ReleaseRequest() { | |
322 return request_.release(); | |
stevenjb
2015/01/05 17:34:22
This should return a scoped_ptr to ensure that own
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
323 } | |
324 | |
325 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone( | |
326 const TimeZoneResponseData* timezone) { | |
327 apply_timezone_.Run(timezone); | |
328 } | |
329 | |
330 // ------------------------------------------------------------------------ | |
331 // TimeZoneResolver implementation | |
332 | |
333 TimeZoneResolver::TimeZoneResolver( | |
334 net::URLRequestContextGetter* context, | |
335 const GURL& url, | |
336 const ApplyTimeZoneCallback& apply_timezone, | |
337 const DelayNetworkCallClosure& delay_network_call) | |
338 : context_(context), | |
339 url_(url), | |
340 apply_timezone_(apply_timezone), | |
341 delay_network_call_(delay_network_call), | |
342 implementation_(nullptr) { | |
343 DCHECK(!apply_timezone.is_null()); | |
344 } | |
345 | |
346 TimeZoneResolver::~TimeZoneResolver() { | |
347 Stop(); | |
348 } | |
349 | |
350 void TimeZoneResolver::Start() { | |
351 DCHECK(thread_checker_.CalledOnValidThread()); | |
352 if (!implementation_) { | |
353 implementation_ = new TimeZoneResolverImpl(context_, url_, apply_timezone_, | |
354 delay_network_call_); | |
355 implementation_->Start(); | |
356 } | |
357 } | |
358 | |
359 void TimeZoneResolver::Stop() { | |
360 DCHECK(thread_checker_.CalledOnValidThread()); | |
361 if (implementation_) { | |
Bernhard Bauer
2015/01/05 10:22:33
This seems like an excellent use case for a scoped
Alexander Alekseev
2015/01/15 18:59:03
Done.
| |
362 delete implementation_; | |
363 implementation_ = nullptr; | |
364 } | |
365 } | |
366 | |
367 } // namespace chromeos | |
OLD | NEW |