// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/gcm_driver/gcm_channel_status_syncer.h" #include "base/bind.h" #include "base/command_line.h" #include "base/location.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/pref_service.h" #include "base/rand_util.h" #include "base/strings/string_number_conversions.h" #include "components/gcm_driver/gcm_channel_status_request.h" #include "components/gcm_driver/gcm_driver.h" #include "components/pref_registry/pref_registry_syncable.h" namespace gcm { namespace { // A small delay to avoid sending request at browser startup time for first-time // request. const int kFirstTimeDelaySeconds = 1 * 60; // 1 minute. // The fuzzing variation added to the polling delay. const int kGCMChannelRequestTimeJitterSeconds = 15 * 60; // 15 minues. // The minimum poll interval that can be overridden to. const int kMinCustomPollIntervalMinutes = 2; // Custom poll interval could not be used more than the limit below. const int kMaxNumberToUseCustomPollInterval = 10; } // namespace namespace prefs { // The GCM channel's enabled state. const char kGCMChannelStatus[] = "gcm.channel_status"; // The GCM channel's polling interval (in seconds). const char kGCMChannelPollIntervalSeconds[] = "gcm.poll_interval"; // Last time when checking with the GCM channel status server is done. const char kGCMChannelLastCheckTime[] = "gcm.check_time"; } // namepsace prefs namespace switches { // Override the default poll interval for testing purpose. const char kCustomPollIntervalMinutes[] = "gcm-channel-poll-interval"; } // namepsace switches // static void GCMChannelStatusSyncer::RegisterPrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(prefs::kGCMChannelStatus, true); registry->RegisterIntegerPref( prefs::kGCMChannelPollIntervalSeconds, GCMChannelStatusRequest::default_poll_interval_seconds()); registry->RegisterInt64Pref(prefs::kGCMChannelLastCheckTime, 0); } // static void GCMChannelStatusSyncer::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { registry->RegisterBooleanPref( prefs::kGCMChannelStatus, true, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); registry->RegisterIntegerPref( prefs::kGCMChannelPollIntervalSeconds, GCMChannelStatusRequest::default_poll_interval_seconds(), user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); registry->RegisterInt64Pref( prefs::kGCMChannelLastCheckTime, 0, user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); } // static int GCMChannelStatusSyncer::first_time_delay_seconds() { return kFirstTimeDelaySeconds; } GCMChannelStatusSyncer::GCMChannelStatusSyncer( GCMDriver* driver, PrefService* prefs, const std::string& channel_status_request_url, const std::string& user_agent, const scoped_refptr& request_context) : driver_(driver), prefs_(prefs), channel_status_request_url_(channel_status_request_url), user_agent_(user_agent), request_context_(request_context), started_(false), gcm_enabled_(true), poll_interval_seconds_( GCMChannelStatusRequest::default_poll_interval_seconds()), custom_poll_interval_use_count_(0), delay_removed_for_testing_(false), weak_ptr_factory_(this) { gcm_enabled_ = prefs_->GetBoolean(prefs::kGCMChannelStatus); poll_interval_seconds_ = prefs_->GetInteger( prefs::kGCMChannelPollIntervalSeconds); if (poll_interval_seconds_ < GCMChannelStatusRequest::min_poll_interval_seconds()) { poll_interval_seconds_ = GCMChannelStatusRequest::min_poll_interval_seconds(); } const base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess(); if (command_line.HasSwitch(switches::kCustomPollIntervalMinutes)) { std::string value(command_line.GetSwitchValueASCII( switches::kCustomPollIntervalMinutes)); int minutes = 0; if (base::StringToInt(value, &minutes)) { DCHECK_GE(minutes, kMinCustomPollIntervalMinutes); if (minutes >= kMinCustomPollIntervalMinutes) { poll_interval_seconds_ = minutes * 60; custom_poll_interval_use_count_ = kMaxNumberToUseCustomPollInterval; } } } last_check_time_ = base::Time::FromInternalValue( prefs_->GetInt64(prefs::kGCMChannelLastCheckTime)); } GCMChannelStatusSyncer::~GCMChannelStatusSyncer() { } void GCMChannelStatusSyncer::EnsureStarted() { // Bail out if the request is already scheduled or started. if (started_) return; started_ = true; ScheduleRequest(); } void GCMChannelStatusSyncer::Stop() { started_ = false; request_.reset(); weak_ptr_factory_.InvalidateWeakPtrs(); } void GCMChannelStatusSyncer::OnRequestCompleted(bool update_received, bool enabled, int poll_interval_seconds) { DCHECK(request_); request_.reset(); // Persist the current time as the last request complete time. last_check_time_ = base::Time::Now(); prefs_->SetInt64(prefs::kGCMChannelLastCheckTime, last_check_time_.ToInternalValue()); if (update_received) { if (gcm_enabled_ != enabled) { gcm_enabled_ = enabled; prefs_->SetBoolean(prefs::kGCMChannelStatus, enabled); if (gcm_enabled_) driver_->Enable(); else driver_->Disable(); } // Skip updating poll interval if the custom one is still in effect. if (!custom_poll_interval_use_count_) { DCHECK_GE(poll_interval_seconds, GCMChannelStatusRequest::min_poll_interval_seconds()); if (poll_interval_seconds_ != poll_interval_seconds) { poll_interval_seconds_ = poll_interval_seconds; prefs_->SetInteger(prefs::kGCMChannelPollIntervalSeconds, poll_interval_seconds_); } } } // Do not schedule next request if syncer is stopped. if (started_) ScheduleRequest(); } void GCMChannelStatusSyncer::ScheduleRequest() { current_request_delay_interval_ = GetRequestDelayInterval(); base::MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&GCMChannelStatusSyncer::StartRequest, weak_ptr_factory_.GetWeakPtr()), current_request_delay_interval_); if (custom_poll_interval_use_count_) custom_poll_interval_use_count_--; } void GCMChannelStatusSyncer::StartRequest() { DCHECK(!request_); request_.reset(new GCMChannelStatusRequest( request_context_, channel_status_request_url_, user_agent_, base::Bind(&GCMChannelStatusSyncer::OnRequestCompleted, weak_ptr_factory_.GetWeakPtr()))); request_->Start(); } base::TimeDelta GCMChannelStatusSyncer::GetRequestDelayInterval() const { // No delay during testing. if (delay_removed_for_testing_) return base::TimeDelta(); // Make sure that checking with server occurs at polling interval, regardless // whether the browser restarts. int64 delay_seconds = poll_interval_seconds_ - (base::Time::Now() - last_check_time_).InSeconds(); if (delay_seconds < 0) delay_seconds = 0; if (last_check_time_.is_null()) { // For the first-time request, add a small delay to avoid sending request at // browser startup time. DCHECK(!delay_seconds); delay_seconds = kFirstTimeDelaySeconds; } else { // Otherwise, add a fuzzing variation to the delay. // The fuzzing variation is off when the custom interval is used. if (!custom_poll_interval_use_count_) delay_seconds += base::RandInt(0, kGCMChannelRequestTimeJitterSeconds); } return base::TimeDelta::FromSeconds(delay_seconds); } } // namespace gcm