Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / chromeos / policy / heartbeat_scheduler.cc
bloba9109649dd59ef2996603b2b0f252dc59fa7615e
1 // Copyright (c) 2015 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.
5 #include "chrome/browser/chromeos/policy/heartbeat_scheduler.h"
7 #include <string>
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/location.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/time/time.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "components/gcm_driver/gcm_driver.h"
19 namespace {
20 const int kMinHeartbeatIntervalMs = 30 * 1000; // 30 seconds
21 const int kMaxHeartbeatIntervalMs = 24 * 60 * 60 * 1000; // 24 hours
23 // Our sender ID we send up with all of our GCM messages.
24 const char* kHeartbeatGCMAppID = "com.google.chromeos.monitoring";
26 // The default destination we send our GCM messages to.
27 const char* kHeartbeatGCMDestinationID = "1013309121859";
28 const char* kHeartbeatGCMSenderSuffix = "@google.com";
30 const char* kMonitoringMessageTypeKey = "type";
31 const char* kHeartbeatTimestampKey = "timestamp";
32 const char* kHeartbeatDomainNameKey = "domain_name";
33 const char* kHeartbeatDeviceIDKey = "device_id";
34 const char* kHeartbeatTypeValue = "hb";
36 // If we get an error registering with GCM, try again in two minutes.
37 const int64 kRegistrationRetryDelayMs = 2 * 60 * 1000;
39 // Returns the destination ID for GCM heartbeats.
40 std::string GetDestinationID() {
41 std::string receiver_id = kHeartbeatGCMDestinationID;
42 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
43 switches::kMonitoringDestinationID)) {
44 receiver_id = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
45 switches::kMonitoringDestinationID);
47 return receiver_id;
50 } // namespace
52 namespace policy {
54 const int64 HeartbeatScheduler::kDefaultHeartbeatIntervalMs =
55 2 * 60 * 1000; // 2 minutes
57 // Helper class used to manage GCM registration (handles retrying after
58 // errors, etc).
59 class HeartbeatRegistrationHelper {
60 public:
61 typedef base::Callback<void(const std::string& registration_id)>
62 RegistrationHelperCallback;
64 HeartbeatRegistrationHelper(
65 gcm::GCMDriver* gcm_driver,
66 const scoped_refptr<base::SequencedTaskRunner>& task_runner);
68 void Register(const RegistrationHelperCallback& callback);
70 private:
71 void AttemptRegistration();
73 // Callback invoked once a registration attempt has finished.
74 void OnRegisterAttemptComplete(const std::string& registration_id,
75 gcm::GCMClient::Result result);
77 // GCMDriver to use to register.
78 gcm::GCMDriver* const gcm_driver_;
80 // Callback to invoke when we have completed GCM registration.
81 RegistrationHelperCallback callback_;
83 // TaskRunner used for scheduling retry attempts.
84 const scoped_refptr<base::SequencedTaskRunner> task_runner_;
86 // Should remain the last member so it will be destroyed first and
87 // invalidate all weak pointers.
88 base::WeakPtrFactory<HeartbeatRegistrationHelper> weak_factory_;
90 DISALLOW_COPY_AND_ASSIGN(HeartbeatRegistrationHelper);
93 HeartbeatRegistrationHelper::HeartbeatRegistrationHelper(
94 gcm::GCMDriver* gcm_driver,
95 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
96 : gcm_driver_(gcm_driver),
97 task_runner_(task_runner),
98 weak_factory_(this) {
101 void HeartbeatRegistrationHelper::Register(
102 const RegistrationHelperCallback& callback) {
103 // Should only call Register() once.
104 DCHECK(callback_.is_null());
105 callback_ = callback;
106 AttemptRegistration();
109 void HeartbeatRegistrationHelper::AttemptRegistration() {
110 std::vector<std::string> destinations;
111 destinations.push_back(GetDestinationID());
112 gcm_driver_->Register(
113 kHeartbeatGCMAppID,
114 destinations,
115 base::Bind(&HeartbeatRegistrationHelper::OnRegisterAttemptComplete,
116 weak_factory_.GetWeakPtr()));
119 void HeartbeatRegistrationHelper::OnRegisterAttemptComplete(
120 const std::string& registration_id, gcm::GCMClient::Result result) {
121 DVLOG(1) << "Received Register() response: " << result;
122 // TODO(atwilson): Track GCM errors via UMA (http://crbug.com/459238).
123 switch (result) {
124 case gcm::GCMClient::SUCCESS:
126 // Copy the callback, because the callback may free this object and
127 // we don't want to free the callback object and any bound variables
128 // until the callback exits.
129 RegistrationHelperCallback callback = callback_;
130 callback.Run(registration_id);
131 // This helper may be freed now, so do not access any member variables
132 // after this point.
133 return;
136 case gcm::GCMClient::NETWORK_ERROR:
137 case gcm::GCMClient::SERVER_ERROR:
138 // Transient error - try again after a delay.
139 task_runner_->PostDelayedTask(
140 FROM_HERE,
141 base::Bind(&HeartbeatRegistrationHelper::AttemptRegistration,
142 weak_factory_.GetWeakPtr()),
143 base::TimeDelta::FromMilliseconds(kRegistrationRetryDelayMs));
144 break;
146 case gcm::GCMClient::INVALID_PARAMETER:
147 case gcm::GCMClient::UNKNOWN_ERROR:
148 case gcm::GCMClient::GCM_DISABLED:
149 // No need to bother retrying in the case of one of these fatal errors.
150 // This means that heartbeats will remain disabled until the next
151 // restart.
152 DLOG(ERROR) << "Fatal GCM Registration error: " << result;
153 break;
155 case gcm::GCMClient::ASYNC_OPERATION_PENDING:
156 case gcm::GCMClient::TTL_EXCEEDED:
157 default:
158 NOTREACHED() << "Unexpected GCMDriver::Register() result: " << result;
159 break;
163 HeartbeatScheduler::HeartbeatScheduler(
164 gcm::GCMDriver* driver,
165 policy::CloudPolicyClient* cloud_policy_client,
166 const std::string& enrollment_domain,
167 const std::string& device_id,
168 const scoped_refptr<base::SequencedTaskRunner>& task_runner)
169 : task_runner_(task_runner),
170 enrollment_domain_(enrollment_domain),
171 device_id_(device_id),
172 heartbeat_enabled_(false),
173 heartbeat_interval_(
174 base::TimeDelta::FromMilliseconds(kDefaultHeartbeatIntervalMs)),
175 cloud_policy_client_(cloud_policy_client),
176 gcm_driver_(driver),
177 weak_factory_(this) {
178 // If no GCMDriver (e.g. this is loaded as part of an unrelated unit test)
179 // do nothing as no heartbeats can be sent.
180 if (!gcm_driver_)
181 return;
183 heartbeat_frequency_observer_ =
184 chromeos::CrosSettings::Get()->AddSettingsObserver(
185 chromeos::kHeartbeatFrequency,
186 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings,
187 base::Unretained(this)));
189 heartbeat_enabled_observer_ =
190 chromeos::CrosSettings::Get()->AddSettingsObserver(
191 chromeos::kHeartbeatEnabled,
192 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings,
193 base::Unretained(this)));
195 // Update the heartbeat frequency from settings. This will trigger a
196 // heartbeat as appropriate once the settings have been refreshed.
197 RefreshHeartbeatSettings();
200 void HeartbeatScheduler::RefreshHeartbeatSettings() {
201 // Attempt to fetch the current value of the reporting settings.
202 // If trusted values are not available, register this function to be called
203 // back when they are available.
204 chromeos::CrosSettings* settings = chromeos::CrosSettings::Get();
205 if (chromeos::CrosSettingsProvider::TRUSTED != settings->PrepareTrustedValues(
206 base::Bind(&HeartbeatScheduler::RefreshHeartbeatSettings,
207 weak_factory_.GetWeakPtr()))) {
208 return;
211 // CrosSettings are trusted - update our cached settings (we cache the
212 // value because CrosSettings can become untrusted at arbitrary times and we
213 // want to use the last trusted value).
214 int frequency;
215 if (settings->GetInteger(chromeos::kHeartbeatFrequency, &frequency))
216 heartbeat_interval_ = EnsureValidHeartbeatInterval(
217 base::TimeDelta::FromMilliseconds(frequency));
219 bool enabled;
220 if (settings->GetBoolean(chromeos::kHeartbeatEnabled, &enabled))
221 heartbeat_enabled_ = enabled;
223 if (!heartbeat_enabled_) {
224 // Heartbeats are no longer enabled - cancel our callback and any
225 // outstanding registration attempts and disconnect from GCM so the
226 // connection can be shut down. If heartbeats are re-enabled later, we
227 // will re-register with GCM.
228 heartbeat_callback_.Cancel();
229 ShutdownGCM();
230 } else {
231 // Schedule a new upload with the new frequency.
232 ScheduleNextHeartbeat();
235 DVLOG(1) << "heartbeat enabled: " << heartbeat_enabled_;
236 DVLOG(1) << "heartbeat frequency: " << heartbeat_interval_;
239 void HeartbeatScheduler::ShutdownGCM() {
240 registration_helper_.reset();
241 registration_id_.clear();
242 if (registered_app_handler_) {
243 registered_app_handler_ = false;
244 gcm_driver_->RemoveAppHandler(kHeartbeatGCMAppID);
248 base::TimeDelta HeartbeatScheduler::EnsureValidHeartbeatInterval(
249 const base::TimeDelta& interval) {
250 const base::TimeDelta min = base::TimeDelta::FromMilliseconds(
251 kMinHeartbeatIntervalMs);
252 const base::TimeDelta max = base::TimeDelta::FromMilliseconds(
253 kMaxHeartbeatIntervalMs);
254 if (interval < min) {
255 DLOG(WARNING) << "Invalid heartbeat interval: " << interval;
256 return min;
258 if (interval > max) {
259 DLOG(WARNING) << "Invalid heartbeat interval: " << interval;
260 return max;
262 return interval;
265 void HeartbeatScheduler::ScheduleNextHeartbeat() {
266 // Do nothing if heartbeats are disabled.
267 if (!heartbeat_enabled_)
268 return;
270 if (registration_id_.empty()) {
271 // We are not registered with the GCM service yet, so kick off registration.
272 if (!registration_helper_) {
273 // Add ourselves as an AppHandler - this is required in order to setup
274 // a GCM connection.
275 registered_app_handler_ = true;
276 gcm_driver_->AddAppHandler(kHeartbeatGCMAppID, this);
277 registration_helper_.reset(new HeartbeatRegistrationHelper(
278 gcm_driver_, task_runner_));
279 registration_helper_->Register(
280 base::Bind(&HeartbeatScheduler::OnRegistrationComplete,
281 weak_factory_.GetWeakPtr()));
283 return;
286 // Calculate when to fire off the next update (if it should have already
287 // happened, this yields a TimeDelta of 0).
288 base::TimeDelta delay = std::max(
289 last_heartbeat_ + heartbeat_interval_ - base::Time::NowFromSystemTime(),
290 base::TimeDelta());
292 heartbeat_callback_.Reset(base::Bind(&HeartbeatScheduler::SendHeartbeat,
293 base::Unretained(this)));
294 task_runner_->PostDelayedTask(
295 FROM_HERE, heartbeat_callback_.callback(), delay);
298 void HeartbeatScheduler::OnRegistrationComplete(
299 const std::string& registration_id) {
300 DCHECK(!registration_id.empty());
301 registration_helper_.reset();
302 registration_id_ = registration_id;
304 if (cloud_policy_client_) {
305 // TODO(binjin): Avoid sending the same GCM id to the server.
306 // See http://crbug.com/516375
307 cloud_policy_client_->UpdateGcmId(
308 registration_id,
309 base::Bind(&HeartbeatScheduler::OnGcmIdUpdateRequestSent,
310 weak_factory_.GetWeakPtr()));
313 // Now that GCM registration is complete, start sending heartbeats.
314 ScheduleNextHeartbeat();
317 void HeartbeatScheduler::SendHeartbeat() {
318 DCHECK(!registration_id_.empty());
319 if (!gcm_driver_ || !heartbeat_enabled_)
320 return;
322 gcm::OutgoingMessage message;
323 message.time_to_live = heartbeat_interval_.InSeconds();
324 // Just use the current timestamp as the message ID - if the user changes the
325 // time and we send a message with the same ID that we previously used, no
326 // big deal (the new message will replace the old, which is the behavior we
327 // want anyway, per:
328 // https://developer.chrome.com/apps/cloudMessaging#send_messages
329 message.id = base::Int64ToString(
330 base::Time::NowFromSystemTime().ToInternalValue());
331 message.data[kMonitoringMessageTypeKey] = kHeartbeatTypeValue;
332 message.data[kHeartbeatTimestampKey] = base::Int64ToString(
333 base::Time::NowFromSystemTime().ToJavaTime());
334 message.data[kHeartbeatDomainNameKey] = enrollment_domain_;
335 message.data[kHeartbeatDeviceIDKey] = device_id_;
336 gcm_driver_->Send(kHeartbeatGCMAppID,
337 GetDestinationID() + kHeartbeatGCMSenderSuffix,
338 message,
339 base::Bind(&HeartbeatScheduler::OnHeartbeatSent,
340 weak_factory_.GetWeakPtr()));
343 void HeartbeatScheduler::OnHeartbeatSent(const std::string& message_id,
344 gcm::GCMClient::Result result) {
345 DVLOG(1) << "Monitoring heartbeat sent - result = " << result;
346 // Don't care if the result was successful or not - just schedule the next
347 // heartbeat.
348 DLOG_IF(ERROR, result != gcm::GCMClient::SUCCESS) <<
349 "Error sending monitoring heartbeat: " << result;
350 last_heartbeat_ = base::Time::NowFromSystemTime();
351 ScheduleNextHeartbeat();
354 HeartbeatScheduler::~HeartbeatScheduler() {
355 ShutdownGCM();
358 void HeartbeatScheduler::ShutdownHandler() {
359 // This should never be called, because BrowserProcessImpl::StartTearDown()
360 // should shutdown the BrowserPolicyConnector (which destroys this object)
361 // before the GCMDriver. Our goal is to make sure that this object is always
362 // shutdown before GCMDriver is shut down, rather than trying to handle the
363 // case when GCMDriver goes away.
364 NOTREACHED() << "HeartbeatScheduler should be destroyed before GCMDriver";
367 void HeartbeatScheduler::OnMessage(const std::string& app_id,
368 const gcm::IncomingMessage& message) {
369 // Should never be called because we don't get any incoming messages
370 // for our app ID.
371 NOTREACHED() << "Received incoming message for " << app_id;
374 void HeartbeatScheduler::OnMessagesDeleted(const std::string& app_id) {
377 void HeartbeatScheduler::OnSendError(
378 const std::string& app_id,
379 const gcm::GCMClient::SendErrorDetails& details) {
380 // Ignore send errors - we already are notified above in OnHeartbeatSent().
383 void HeartbeatScheduler::OnSendAcknowledged(const std::string& app_id,
384 const std::string& message_id) {
385 DVLOG(1) << "Heartbeat sent with message_id: " << message_id;
388 void HeartbeatScheduler::OnGcmIdUpdateRequestSent(bool success) {
389 // TODO(binjin): Handle the failure, probably by exponential backoff.
390 LOG_IF(WARNING, !success) << "Failed to send GCM id to DM server";
393 } // namespace policy