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
20namespace gcm {
21
22namespace {
23
24// The GCM channel's enabled state.
25const char kGCMChannelStatus[] = "gcm.channel_status";
26
27// The GCM channel's polling interval (in seconds).
28const char kGCMChannelPollIntervalSeconds[] = "gcm.poll_interval";
29
30// Last time when checking with the GCM channel status server is done.
31const char kGCMChannelLastCheckTime[] = "gcm.check_time";
32
33// A small delay to avoid sending request at browser startup time for first-time
34// request.
35const int kFirstTimeDelaySeconds = 1 * 60;  // 1 minute.
36
37// The fuzzing variation added to the polling delay.
38const int kGCMChannelRequestTimeJitterSeconds = 15 * 60;  // 15 minues.
39
40// The minimum poll interval that can be overridden to.
41const int kMinCustomPollIntervalMinutes = 2;
42
43// Custom poll interval could not be used more than the limit below.
44const int kMaxNumberToUseCustomPollInterval = 10;
45
46}  // namespace
47
48namespace switches {
49
50// Override the default poll interval for testing purpose.
51const char kCustomPollIntervalMinutes[] = "gcm-channel-poll-interval";
52
53}  // namepsace switches
54
55// static
56void 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
65void 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
82int GCMChannelStatusSyncer::first_time_delay_seconds() {
83  return kFirstTimeDelaySeconds;
84}
85
86GCMChannelStatusSyncer::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
128GCMChannelStatusSyncer::~GCMChannelStatusSyncer() {
129}
130
131void 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
140void GCMChannelStatusSyncer::Stop() {
141  started_ = false;
142  request_.reset();
143  weak_ptr_factory_.InvalidateWeakPtrs();
144}
145
146void 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
184void 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
196void 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
208base::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