Chromium Code Reviews| 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 |