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

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, 11 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/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 once each this interval.
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.
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);
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);
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
67 : public base::PowerObserver,
68 public base::SupportsWeakPtr<TimeZoneResolver::TimeZoneResolverImpl> {
Bernhard Bauer 2015/01/19 09:55:54 Can you use a base::WeakPtrFactory<> as a member i
Alexander Alekseev 2015/01/20 14:41:45 Done.
69 public:
70 TimeZoneResolverImpl(
71 net::URLRequestContextGetter* url_context_getter,
72 const GURL& url,
73 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
74 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call);
75
76 ~TimeZoneResolverImpl() override;
77
78 // This is called once after the object is created.
79 void Start();
80
81 // PowerObserver implementation.
82 void OnResume() override;
83
84 // (Re)Starts timer.
85 void ScheduleRequest();
86
87 // Creates new TZRequest.
88 void CreateNewRequest();
89
90 // Called by TZRequest.
91 SimpleGeolocationProvider* geolocation_provider() {
92 return &geolocation_provider_;
93 }
94 TimeZoneProvider* timezone_provider() { return &timezone_provider_; }
95
96 // Update requests count and last request time.
97 void RecordAttempt();
98
99 // This is called by TZRequest. Destroys active request and starts a new one.
100 void RequestIsFinished();
101
102 void ApplyTimeZone(const TimeZoneResponseData* timezone);
103
104 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const {
105 return delay_network_call_;
106 }
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 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl);
130 };
131
132 namespace {
133
134 // This class implements a single timezone refresh attempt.
135 class TZRequest : public base::SupportsWeakPtr<TZRequest> {
136 public:
137 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver)
138 : resolver_(resolver) {}
139
140 ~TZRequest();
141
142 // Starts request after specified delay.
143 void Start();
144
145 // Called from SimpleGeolocationProvider when location is resolved.
146 void OnLocationResolved(const Geoposition& position,
147 bool server_error,
148 const base::TimeDelta elapsed);
149
150 // TimeZoneRequest::TimeZoneResponseCallback implementation.
151 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
152 bool server_error);
153
154 private:
155 // This is called by network detector when network is available.
156 void StartRequestOnNetworkAvailable();
157
158 TimeZoneResolver::TimeZoneResolverImpl* const resolver_;
159
160 DISALLOW_COPY_AND_ASSIGN(TZRequest);
161 };
162
163 TZRequest::~TZRequest() {
164 }
165
166 void TZRequest::StartRequestOnNetworkAvailable() {
167 resolver_->RecordAttempt();
168 resolver_->geolocation_provider()->RequestGeolocation(
169 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds),
170 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr()));
171 }
172
173 void TZRequest::Start() {
174 // call to chromeos::DelayNetworkCall
175 resolver_->delay_network_call().Run(
176 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr()));
177 }
178
179 void TZRequest::OnLocationResolved(const Geoposition& position,
180 bool server_error,
181 const base::TimeDelta elapsed) {
182 base::ScopedClosureRunner on_request_finished(
183 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
184 resolver_->AsWeakPtr()));
Bernhard Bauer 2015/01/19 09:55:54 You can use |resolver_| unretained here. If it's n
Alexander Alekseev 2015/01/20 14:41:45 Done.
185
186 // Ignore invalid position.
187 if (!position.Valid())
188 return;
189
190 const base::TimeDelta timeout =
191 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds);
192
193 if (elapsed >= timeout) {
194 VLOG(1) << "Refresh TimeZone: got location after timeout ("
195 << elapsed.InSecondsF() << " seconds elapsed). Ignored.";
196 return;
197 }
198
199 resolver_->timezone_provider()->RequestTimezone(
200 position,
201 false, // sensor
202 timeout - elapsed,
203 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr()));
204
205 // Prevent |on_request_finished| from firing here.
206 base::Closure unused = on_request_finished.Release();
207 }
208
209 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone,
210 bool server_error) {
211 base::ScopedClosureRunner on_request_finished(
212 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished,
213 resolver_->AsWeakPtr()));
214
215 DCHECK(timezone);
216 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug()
217 << "}.";
218
219 if (timezone->status != TimeZoneResponseData::OK) {
220 VLOG(1) << "Refresh TimeZone: failed to resolve timezone.";
221 return;
222 }
223
224 resolver_->ApplyTimeZone(timezone.get());
225 }
226
227 } // anonymous namespace
228
229 // ------------------------------------------------------------------------
230 // TimeZoneResolver::TimeZoneResolverImpl implementation.
231
232 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl(
233 net::URLRequestContextGetter* url_context_getter,
234 const GURL& url,
235 const TimeZoneResolver::ApplyTimeZoneCallback& apply_timezone,
236 const TimeZoneResolver::DelayNetworkCallClosure& delay_network_call)
237 : url_context_getter_(url_context_getter),
238 url_(url),
239 apply_timezone_(apply_timezone),
240 delay_network_call_(delay_network_call),
241 geolocation_provider_(
242 url_context_getter,
243 SimpleGeolocationProvider::DefaultGeolocationProviderURL()),
244 timezone_provider_(url_context_getter, DefaultTimezoneProviderURL()),
245 requests_count_(0) {
246 DCHECK(!apply_timezone.is_null());
247 DCHECK(!delay_network_call.is_null());
248
249 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
250 power_monitor->AddObserver(this);
251 }
252
253 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() {
254 base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
255 if (power_monitor)
256 power_monitor->RemoveObserver(this);
257 }
258
259 void TimeZoneResolver::TimeZoneResolverImpl::Start() {
260 // Start() is usually called twice:
261 // - On device boot.
262 // - On user session start.
263 if (request_ || refresh_timer_.IsRunning())
264 return;
265
266 ScheduleRequest();
267 }
268
269 // Returns delay to next timezone update request
270 base::TimeDelta
271 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() {
272 const base::TimeDelta initial_interval =
273 base::TimeDelta::FromMilliseconds(kInitialRefreshIntervalMS);
274 const base::TimeDelta maximum_interval =
275 base::TimeDelta::FromMilliseconds(kMaximalRefreshIntervalMS);
276
277 // This is initial request, which should be served immediately.
278 if (requests_count_ == 0)
279 return initial_interval;
280
281 // See comment to kRefreshIntervalRequestsCountMultiplier.
282 if (requests_count_ >=
283 MaxRequestsCountForInterval(maximum_interval.InSecondsF())) {
284 return maximum_interval;
285 }
286
287 const base::TimeDelta interval(
288 base::TimeDelta::FromSeconds(IntervalForNextRequest(requests_count_)));
289 DCHECK_LE(interval, maximum_interval);
290 return interval;
291 }
292
293 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() {
294 requests_count_ = 0;
295 // Refresh timezone immediately.
296 request_.reset();
297 ScheduleRequest();
298 }
299
300 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() {
301 if (request_)
302 return;
303
304 // base::OneShotTimer
305 base::TimeDelta interval = CalculateNextInterval();
306 refresh_timer_.Stop();
307 refresh_timer_.Start(
308 FROM_HERE, interval,
309 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest,
310 AsWeakPtr()));
311 }
312
313 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() {
314 if (request_)
315 return;
316
317 refresh_timer_.Stop();
318
319 request_.reset(new TZRequest(this));
320 request_->Start();
321 }
322
323 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() {
324 ++requests_count_;
325 }
326
327 void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() {
328 request_.reset();
329 ScheduleRequest();
330 }
331
332 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone(
333 const TimeZoneResponseData* timezone) {
334 apply_timezone_.Run(timezone);
335 }
336
337 // ------------------------------------------------------------------------
338 // TimeZoneResolver implementation
339
340 TimeZoneResolver::TimeZoneResolver(
341 net::URLRequestContextGetter* context,
342 const GURL& url,
343 const ApplyTimeZoneCallback& apply_timezone,
344 const DelayNetworkCallClosure& delay_network_call)
345 : context_(context),
346 url_(url),
347 apply_timezone_(apply_timezone),
348 delay_network_call_(delay_network_call) {
349 DCHECK(!apply_timezone.is_null());
350 }
351
352 TimeZoneResolver::~TimeZoneResolver() {
353 Stop();
354 }
355
356 void TimeZoneResolver::Start() {
357 DCHECK(thread_checker_.CalledOnValidThread());
358 if (!implementation_) {
359 implementation_.reset(new TimeZoneResolverImpl(
360 context_, url_, apply_timezone_, delay_network_call_));
361 implementation_->Start();
362 }
363 }
364
365 void TimeZoneResolver::Stop() {
366 DCHECK(thread_checker_.CalledOnValidThread());
367 implementation_.reset();
368 }
369
370 // static
371 int TimeZoneResolver::MaxRequestsCountForIntervalForTesting(
372 const double interval_seconds) {
373 return MaxRequestsCountForInterval(interval_seconds);
374 }
375
376 // static
377 int TimeZoneResolver::IntervalForNextRequestForTesting(const int requests) {
378 return IntervalForNextRequest(requests);
379 }
380
381 } // 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