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 "components/gcm_driver/gcm_channel_status_syncer.h" 6 7 #include "base/bind.h" 8 #include "base/command_line.h" 9 #include "base/location.h" 10 #include "base/logging.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/prefs/pref_registry_simple.h" 13 #include "base/prefs/pref_service.h" 14 #include "base/rand_util.h" 15 #include "base/strings/string_number_conversions.h" 16 #include "components/gcm_driver/gcm_channel_status_request.h" 17 #include "components/gcm_driver/gcm_driver.h" 18 #include "components/pref_registry/pref_registry_syncable.h" 19 20 namespace gcm { 21 22 namespace { 23 24 // The GCM channel's enabled state. 25 const char kGCMChannelStatus[] = "gcm.channel_status"; 26 27 // The GCM channel's polling interval (in seconds). 28 const char kGCMChannelPollIntervalSeconds[] = "gcm.poll_interval"; 29 30 // Last time when checking with the GCM channel status server is done. 31 const char kGCMChannelLastCheckTime[] = "gcm.check_time"; 32 33 // A small delay to avoid sending request at browser startup time for first-time 34 // request. 35 const int kFirstTimeDelaySeconds = 1 * 60; // 1 minute. 36 37 // The fuzzing variation added to the polling delay. 38 const int kGCMChannelRequestTimeJitterSeconds = 15 * 60; // 15 minues. 39 40 // The minimum poll interval that can be overridden to. 41 const int kMinCustomPollIntervalMinutes = 2; 42 43 // Custom poll interval could not be used more than the limit below. 44 const int kMaxNumberToUseCustomPollInterval = 10; 45 46 } // namespace 47 48 namespace switches { 49 50 // Override the default poll interval for testing purpose. 51 const char kCustomPollIntervalMinutes[] = "gcm-channel-poll-interval"; 52 53 } // namepsace switches 54 55 // static 56 void GCMChannelStatusSyncer::RegisterPrefs(PrefRegistrySimple* registry) { 57 registry->RegisterBooleanPref(kGCMChannelStatus, true); 58 registry->RegisterIntegerPref( 59 kGCMChannelPollIntervalSeconds, 60 GCMChannelStatusRequest::default_poll_interval_seconds()); 61 registry->RegisterInt64Pref(kGCMChannelLastCheckTime, 0); 62 } 63 64 // static 65 void GCMChannelStatusSyncer::RegisterProfilePrefs( 66 user_prefs::PrefRegistrySyncable* registry) { 67 registry->RegisterBooleanPref( 68 kGCMChannelStatus, 69 true, 70 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 71 registry->RegisterIntegerPref( 72 kGCMChannelPollIntervalSeconds, 73 GCMChannelStatusRequest::default_poll_interval_seconds(), 74 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 75 registry->RegisterInt64Pref( 76 kGCMChannelLastCheckTime, 77 0, 78 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 79 } 80 81 // static 82 int GCMChannelStatusSyncer::first_time_delay_seconds() { 83 return kFirstTimeDelaySeconds; 84 } 85 86 GCMChannelStatusSyncer::GCMChannelStatusSyncer( 87 GCMDriver* driver, 88 PrefService* prefs, 89 const std::string& channel_status_request_url, 90 const std::string& user_agent, 91 const scoped_refptr<net::URLRequestContextGetter>& request_context) 92 : driver_(driver), 93 prefs_(prefs), 94 channel_status_request_url_(channel_status_request_url), 95 user_agent_(user_agent), 96 request_context_(request_context), 97 started_(false), 98 gcm_enabled_(true), 99 poll_interval_seconds_( 100 GCMChannelStatusRequest::default_poll_interval_seconds()), 101 custom_poll_interval_use_count_(0), 102 delay_removed_for_testing_(false), 103 weak_ptr_factory_(this) { 104 gcm_enabled_ = prefs_->GetBoolean(kGCMChannelStatus); 105 poll_interval_seconds_ = prefs_->GetInteger(kGCMChannelPollIntervalSeconds); 106 if (poll_interval_seconds_ < 107 GCMChannelStatusRequest::min_poll_interval_seconds()) { 108 poll_interval_seconds_ = 109 GCMChannelStatusRequest::min_poll_interval_seconds(); 110 } 111 if (CommandLine::ForCurrentProcess()->HasSwitch( 112 switches::kCustomPollIntervalMinutes)) { 113 std::string value(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 114 switches::kCustomPollIntervalMinutes)); 115 int minutes = 0; 116 if (base::StringToInt(value, &minutes)) { 117 DCHECK_GE(minutes, kMinCustomPollIntervalMinutes); 118 if (minutes >= kMinCustomPollIntervalMinutes) { 119 poll_interval_seconds_ = minutes * 60; 120 custom_poll_interval_use_count_ = kMaxNumberToUseCustomPollInterval; 121 } 122 } 123 } 124 last_check_time_ = base::Time::FromInternalValue( 125 prefs_->GetInt64(kGCMChannelLastCheckTime)); 126 } 127 128 GCMChannelStatusSyncer::~GCMChannelStatusSyncer() { 129 } 130 131 void GCMChannelStatusSyncer::EnsureStarted() { 132 // Bail out if the request is already scheduled or started. 133 if (started_) 134 return; 135 started_ = true; 136 137 ScheduleRequest(); 138 } 139 140 void GCMChannelStatusSyncer::Stop() { 141 started_ = false; 142 request_.reset(); 143 weak_ptr_factory_.InvalidateWeakPtrs(); 144 } 145 146 void GCMChannelStatusSyncer::OnRequestCompleted(bool update_received, 147 bool enabled, 148 int poll_interval_seconds) { 149 DCHECK(request_); 150 request_.reset(); 151 152 // Persist the current time as the last request complete time. 153 last_check_time_ = base::Time::Now(); 154 prefs_->SetInt64(kGCMChannelLastCheckTime, 155 last_check_time_.ToInternalValue()); 156 157 if (update_received) { 158 if (gcm_enabled_ != enabled) { 159 gcm_enabled_ = enabled; 160 prefs_->SetBoolean(kGCMChannelStatus, enabled); 161 if (gcm_enabled_) 162 driver_->Enable(); 163 else 164 driver_->Disable(); 165 } 166 167 // Skip updating poll interval if the custom one is still in effect. 168 if (!custom_poll_interval_use_count_) { 169 DCHECK_GE(poll_interval_seconds, 170 GCMChannelStatusRequest::min_poll_interval_seconds()); 171 if (poll_interval_seconds_ != poll_interval_seconds) { 172 poll_interval_seconds_ = poll_interval_seconds; 173 prefs_->SetInteger(kGCMChannelPollIntervalSeconds, 174 poll_interval_seconds_); 175 } 176 } 177 } 178 179 // Do not schedule next request if syncer is stopped. 180 if (started_) 181 ScheduleRequest(); 182 } 183 184 void GCMChannelStatusSyncer::ScheduleRequest() { 185 current_request_delay_interval_ = GetRequestDelayInterval(); 186 base::MessageLoop::current()->PostDelayedTask( 187 FROM_HERE, 188 base::Bind(&GCMChannelStatusSyncer::StartRequest, 189 weak_ptr_factory_.GetWeakPtr()), 190 current_request_delay_interval_); 191 192 if (custom_poll_interval_use_count_) 193 custom_poll_interval_use_count_--; 194 } 195 196 void GCMChannelStatusSyncer::StartRequest() { 197 DCHECK(!request_); 198 199 request_.reset(new GCMChannelStatusRequest( 200 request_context_, 201 channel_status_request_url_, 202 user_agent_, 203 base::Bind(&GCMChannelStatusSyncer::OnRequestCompleted, 204 weak_ptr_factory_.GetWeakPtr()))); 205 request_->Start(); 206 } 207 208 base::TimeDelta GCMChannelStatusSyncer::GetRequestDelayInterval() const { 209 // No delay during testing. 210 if (delay_removed_for_testing_) 211 return base::TimeDelta(); 212 213 // Make sure that checking with server occurs at polling interval, regardless 214 // whether the browser restarts. 215 int64 delay_seconds = poll_interval_seconds_ - 216 (base::Time::Now() - last_check_time_).InSeconds(); 217 if (delay_seconds < 0) 218 delay_seconds = 0; 219 220 if (last_check_time_.is_null()) { 221 // For the first-time request, add a small delay to avoid sending request at 222 // browser startup time. 223 DCHECK(!delay_seconds); 224 delay_seconds = kFirstTimeDelaySeconds; 225 } else { 226 // Otherwise, add a fuzzing variation to the delay. 227 // The fuzzing variation is off when the custom interval is used. 228 if (!custom_poll_interval_use_count_) 229 delay_seconds += base::RandInt(0, kGCMChannelRequestTimeJitterSeconds); 230 } 231 232 return base::TimeDelta::FromSeconds(delay_seconds); 233 } 234 235 } // namespace gcm 236