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.0; |
| 39 |
| 40 // Timezone refresh happens at least once each this interval. |
| 41 const double kMaximumRefreshIntervalSec = 6.0 * 3600; // 6 hours |
| 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 = |
| 61 10 * 60.0; // 10 minutes |
| 62 const unsigned int kRefreshTimeZoneInitialRequestCountOnRateLimit = 2; |
| 63 |
| 64 int MaxRequestsCountForInterval(const double interval_seconds) { |
| 65 return log2(interval_seconds / kInitialRefreshIntervalSec) / |
| 66 kRefreshIntervalRequestsCountMultiplier; |
| 67 } |
| 68 |
| 69 int IntervalForNextRequest(const int requests) { |
| 70 const base::TimeDelta initial_interval = |
| 71 base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec); |
| 72 return static_cast<int>(initial_interval.InSecondsF() * |
| 73 (2 << (static_cast<unsigned>(requests) * |
| 74 kRefreshIntervalRequestsCountMultiplier))); |
| 75 } |
| 76 |
| 77 } // anonymous namespace |
| 78 |
| 79 const char TimeZoneResolver::kLastTimeZoneRefreshTime[] = |
| 80 "timezone_resolver.last_update_time"; |
| 81 |
| 82 // This class periodically refreshes location and timezone. |
| 83 // It should be destroyed to stop refresh. |
| 84 class TimeZoneResolver::TimeZoneResolverImpl : public base::PowerObserver { |
| 85 public: |
| 86 explicit TimeZoneResolverImpl(const TimeZoneResolver* resolver); |
| 87 |
| 88 ~TimeZoneResolverImpl() override; |
| 89 |
| 90 // This is called once after the object is created. |
| 91 void Start(); |
| 92 |
| 93 // PowerObserver implementation. |
| 94 void OnResume() override; |
| 95 |
| 96 // (Re)Starts timer. |
| 97 void ScheduleRequest(); |
| 98 |
| 99 // Creates new TZRequest. |
| 100 void CreateNewRequest(); |
| 101 |
| 102 // Called by TZRequest. |
| 103 SimpleGeolocationProvider* geolocation_provider() { |
| 104 return &geolocation_provider_; |
| 105 } |
| 106 TimeZoneProvider* timezone_provider() { return &timezone_provider_; } |
| 107 |
| 108 // Update requests count and last request time. |
| 109 void RecordAttempt(); |
| 110 |
| 111 // This is called by TZRequest. Destroys active request and starts a new one. |
| 112 void RequestIsFinished(); |
| 113 |
| 114 void ApplyTimeZone(const TimeZoneResponseData* timezone); |
| 115 |
| 116 TimeZoneResolver::DelayNetworkCallClosure delay_network_call() const { |
| 117 return resolver_->delay_network_call(); |
| 118 } |
| 119 |
| 120 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> AsWeakPtr(); |
| 121 |
| 122 private: |
| 123 const TimeZoneResolver* resolver_; |
| 124 |
| 125 // Returns delay to next timezone update request |
| 126 base::TimeDelta CalculateNextInterval(); |
| 127 |
| 128 SimpleGeolocationProvider geolocation_provider_; |
| 129 TimeZoneProvider timezone_provider_; |
| 130 |
| 131 base::OneShotTimer<TimeZoneResolver::TimeZoneResolverImpl> refresh_timer_; |
| 132 |
| 133 // Total number of request attempts. |
| 134 int requests_count_; |
| 135 |
| 136 // This is not NULL when update is in progress. |
| 137 scoped_ptr<TZRequest> request_; |
| 138 |
| 139 base::WeakPtrFactory<TimeZoneResolver::TimeZoneResolverImpl> |
| 140 weak_ptr_factory_; |
| 141 |
| 142 DISALLOW_COPY_AND_ASSIGN(TimeZoneResolverImpl); |
| 143 }; |
| 144 |
| 145 namespace { |
| 146 |
| 147 // This class implements a single timezone refresh attempt. |
| 148 class TZRequest { |
| 149 public: |
| 150 explicit TZRequest(TimeZoneResolver::TimeZoneResolverImpl* resolver) |
| 151 : resolver_(resolver), weak_ptr_factory_(this) {} |
| 152 |
| 153 ~TZRequest(); |
| 154 |
| 155 // Starts request after specified delay. |
| 156 void Start(); |
| 157 |
| 158 // Called from SimpleGeolocationProvider when location is resolved. |
| 159 void OnLocationResolved(const Geoposition& position, |
| 160 bool server_error, |
| 161 const base::TimeDelta elapsed); |
| 162 |
| 163 // TimeZoneRequest::TimeZoneResponseCallback implementation. |
| 164 void OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone, |
| 165 bool server_error); |
| 166 |
| 167 base::WeakPtr<TZRequest> AsWeakPtr(); |
| 168 |
| 169 private: |
| 170 // This is called by network detector when network is available. |
| 171 void StartRequestOnNetworkAvailable(); |
| 172 |
| 173 TimeZoneResolver::TimeZoneResolverImpl* const resolver_; |
| 174 |
| 175 base::WeakPtrFactory<TZRequest> weak_ptr_factory_; |
| 176 |
| 177 DISALLOW_COPY_AND_ASSIGN(TZRequest); |
| 178 }; |
| 179 |
| 180 TZRequest::~TZRequest() { |
| 181 } |
| 182 |
| 183 void TZRequest::StartRequestOnNetworkAvailable() { |
| 184 resolver_->RecordAttempt(); |
| 185 resolver_->geolocation_provider()->RequestGeolocation( |
| 186 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds), |
| 187 base::Bind(&TZRequest::OnLocationResolved, AsWeakPtr())); |
| 188 } |
| 189 |
| 190 void TZRequest::Start() { |
| 191 // call to chromeos::DelayNetworkCall |
| 192 resolver_->delay_network_call().Run( |
| 193 base::Bind(&TZRequest::StartRequestOnNetworkAvailable, AsWeakPtr())); |
| 194 } |
| 195 |
| 196 void TZRequest::OnLocationResolved(const Geoposition& position, |
| 197 bool server_error, |
| 198 const base::TimeDelta elapsed) { |
| 199 base::ScopedClosureRunner on_request_finished( |
| 200 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished, |
| 201 base::Unretained(resolver_))); |
| 202 |
| 203 // Ignore invalid position. |
| 204 if (!position.Valid()) |
| 205 return; |
| 206 |
| 207 const base::TimeDelta timeout = |
| 208 base::TimeDelta::FromSeconds(kRefreshTimeZoneTimeoutSeconds); |
| 209 |
| 210 if (elapsed >= timeout) { |
| 211 VLOG(1) << "Refresh TimeZone: got location after timeout (" |
| 212 << elapsed.InSecondsF() << " seconds elapsed). Ignored."; |
| 213 return; |
| 214 } |
| 215 |
| 216 resolver_->timezone_provider()->RequestTimezone( |
| 217 position, |
| 218 timeout - elapsed, |
| 219 base::Bind(&TZRequest::OnTimezoneResolved, AsWeakPtr())); |
| 220 |
| 221 // Prevent |on_request_finished| from firing here. |
| 222 base::Closure unused = on_request_finished.Release(); |
| 223 } |
| 224 |
| 225 void TZRequest::OnTimezoneResolved(scoped_ptr<TimeZoneResponseData> timezone, |
| 226 bool server_error) { |
| 227 base::ScopedClosureRunner on_request_finished( |
| 228 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished, |
| 229 base::Unretained(resolver_))); |
| 230 |
| 231 DCHECK(timezone); |
| 232 VLOG(1) << "Refreshed local timezone={" << timezone->ToStringForDebug() |
| 233 << "}."; |
| 234 |
| 235 if (timezone->status != TimeZoneResponseData::OK) { |
| 236 VLOG(1) << "Refresh TimeZone: failed to resolve timezone."; |
| 237 return; |
| 238 } |
| 239 |
| 240 resolver_->ApplyTimeZone(timezone.get()); |
| 241 } |
| 242 |
| 243 base::WeakPtr<TZRequest> TZRequest::AsWeakPtr() { |
| 244 return weak_ptr_factory_.GetWeakPtr(); |
| 245 } |
| 246 |
| 247 } // anonymous namespace |
| 248 |
| 249 // ------------------------------------------------------------------------ |
| 250 // TimeZoneResolver::TimeZoneResolverImpl implementation. |
| 251 |
| 252 TimeZoneResolver::TimeZoneResolverImpl::TimeZoneResolverImpl( |
| 253 const TimeZoneResolver* resolver) |
| 254 : resolver_(resolver), |
| 255 geolocation_provider_( |
| 256 resolver->context().get(), |
| 257 SimpleGeolocationProvider::DefaultGeolocationProviderURL()), |
| 258 timezone_provider_(resolver->context().get(), |
| 259 DefaultTimezoneProviderURL()), |
| 260 requests_count_(0), |
| 261 weak_ptr_factory_(this) { |
| 262 DCHECK(!resolver_->apply_timezone().is_null()); |
| 263 DCHECK(!resolver_->delay_network_call().is_null()); |
| 264 |
| 265 base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); |
| 266 power_monitor->AddObserver(this); |
| 267 |
| 268 const int64 last_refresh_at_raw = |
| 269 resolver_->local_state()->GetInt64(kLastTimeZoneRefreshTime); |
| 270 const base::Time last_refresh_at = |
| 271 base::Time::FromInternalValue(last_refresh_at_raw); |
| 272 const base::Time next_refresh_not_before = |
| 273 last_refresh_at + |
| 274 base::TimeDelta::FromSecondsD(kRefreshTimeZoneMinimumDelayOnRestartSec); |
| 275 if (next_refresh_not_before > base::Time::Now()) { |
| 276 requests_count_ = kRefreshTimeZoneInitialRequestCountOnRateLimit; |
| 277 VLOG(1) << "TimeZoneResolverImpl(): initialize requests_count_=" |
| 278 << requests_count_ << " because of rate limit."; |
| 279 } |
| 280 } |
| 281 |
| 282 TimeZoneResolver::TimeZoneResolverImpl::~TimeZoneResolverImpl() { |
| 283 base::PowerMonitor* power_monitor = base::PowerMonitor::Get(); |
| 284 if (power_monitor) |
| 285 power_monitor->RemoveObserver(this); |
| 286 } |
| 287 |
| 288 void TimeZoneResolver::TimeZoneResolverImpl::Start() { |
| 289 // Start() is usually called twice: |
| 290 // - On device boot. |
| 291 // - On user session start. |
| 292 if (request_ || refresh_timer_.IsRunning()) |
| 293 return; |
| 294 |
| 295 ScheduleRequest(); |
| 296 } |
| 297 |
| 298 // Returns delay to next timezone update request |
| 299 base::TimeDelta |
| 300 TimeZoneResolver::TimeZoneResolverImpl::CalculateNextInterval() { |
| 301 // This is initial request, which should be served immediately. |
| 302 if (requests_count_ == 0) { |
| 303 return base::TimeDelta::FromSecondsD(kInitialRefreshIntervalSec); |
| 304 } |
| 305 |
| 306 // See comment to kRefreshIntervalRequestsCountMultiplier. |
| 307 if (requests_count_ >= |
| 308 MaxRequestsCountForInterval(kMaximumRefreshIntervalSec)) { |
| 309 return base::TimeDelta::FromSecondsD(kMaximumRefreshIntervalSec); |
| 310 } |
| 311 |
| 312 const int base_interval = IntervalForNextRequest(requests_count_); |
| 313 DCHECK_LE(base_interval, kMaximumRefreshIntervalSec); |
| 314 |
| 315 // Add jitter to level request rate. |
| 316 const base::TimeDelta interval( |
| 317 base::TimeDelta::FromSecondsD(base::RandDouble() * 2 * base_interval)); |
| 318 VLOG(1) << "TimeZoneResolverImpl::CalculateNextInterval(): interval=" |
| 319 << interval.InSecondsF(); |
| 320 return interval; |
| 321 } |
| 322 |
| 323 void TimeZoneResolver::TimeZoneResolverImpl::OnResume() { |
| 324 requests_count_ = 0; |
| 325 // Refresh timezone immediately. |
| 326 request_.reset(); |
| 327 ScheduleRequest(); |
| 328 } |
| 329 |
| 330 void TimeZoneResolver::TimeZoneResolverImpl::ScheduleRequest() { |
| 331 if (request_) |
| 332 return; |
| 333 |
| 334 // base::OneShotTimer |
| 335 base::TimeDelta interval = CalculateNextInterval(); |
| 336 refresh_timer_.Stop(); |
| 337 refresh_timer_.Start( |
| 338 FROM_HERE, interval, |
| 339 base::Bind(&TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest, |
| 340 AsWeakPtr())); |
| 341 } |
| 342 |
| 343 void TimeZoneResolver::TimeZoneResolverImpl::CreateNewRequest() { |
| 344 if (request_) |
| 345 return; |
| 346 |
| 347 refresh_timer_.Stop(); |
| 348 |
| 349 request_.reset(new TZRequest(this)); |
| 350 request_->Start(); |
| 351 } |
| 352 |
| 353 void TimeZoneResolver::TimeZoneResolverImpl::RecordAttempt() { |
| 354 resolver_->local_state()->SetInt64(kLastTimeZoneRefreshTime, |
| 355 base::Time::Now().ToInternalValue()); |
| 356 ++requests_count_; |
| 357 } |
| 358 |
| 359 void TimeZoneResolver::TimeZoneResolverImpl::RequestIsFinished() { |
| 360 request_.reset(); |
| 361 ScheduleRequest(); |
| 362 } |
| 363 |
| 364 void TimeZoneResolver::TimeZoneResolverImpl::ApplyTimeZone( |
| 365 const TimeZoneResponseData* timezone) { |
| 366 resolver_->apply_timezone().Run(timezone); |
| 367 } |
| 368 |
| 369 base::WeakPtr<TimeZoneResolver::TimeZoneResolverImpl> |
| 370 TimeZoneResolver::TimeZoneResolverImpl::AsWeakPtr() { |
| 371 return weak_ptr_factory_.GetWeakPtr(); |
| 372 } |
| 373 |
| 374 // ------------------------------------------------------------------------ |
| 375 // TimeZoneResolver implementation |
| 376 |
| 377 TimeZoneResolver::TimeZoneResolver( |
| 378 scoped_refptr<net::URLRequestContextGetter> context, |
| 379 const GURL& url, |
| 380 const ApplyTimeZoneCallback& apply_timezone, |
| 381 const DelayNetworkCallClosure& delay_network_call, |
| 382 PrefService* local_state) |
| 383 : context_(context), |
| 384 url_(url), |
| 385 apply_timezone_(apply_timezone), |
| 386 delay_network_call_(delay_network_call), |
| 387 local_state_(local_state) { |
| 388 DCHECK(!apply_timezone.is_null()); |
| 389 } |
| 390 |
| 391 TimeZoneResolver::~TimeZoneResolver() { |
| 392 Stop(); |
| 393 } |
| 394 |
| 395 void TimeZoneResolver::Start() { |
| 396 DCHECK(thread_checker_.CalledOnValidThread()); |
| 397 if (!implementation_) { |
| 398 implementation_.reset(new TimeZoneResolverImpl(this)); |
| 399 implementation_->Start(); |
| 400 } |
| 401 } |
| 402 |
| 403 void TimeZoneResolver::Stop() { |
| 404 DCHECK(thread_checker_.CalledOnValidThread()); |
| 405 implementation_.reset(); |
| 406 } |
| 407 |
| 408 // static |
| 409 int TimeZoneResolver::MaxRequestsCountForIntervalForTesting( |
| 410 const double interval_seconds) { |
| 411 return MaxRequestsCountForInterval(interval_seconds); |
| 412 } |
| 413 |
| 414 // static |
| 415 int TimeZoneResolver::IntervalForNextRequestForTesting(const int requests) { |
| 416 return IntervalForNextRequest(requests); |
| 417 } |
| 418 |
| 419 // static |
| 420 void TimeZoneResolver::RegisterPrefs(PrefRegistrySimple* registry) { |
| 421 registry->RegisterInt64Pref(kLastTimeZoneRefreshTime, 0); |
| 422 } |
| 423 |
| 424 } // namespace chromeos |
OLD | NEW |