1// Copyright 2013 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 "chrome/browser/drive/drive_notification_manager.h"
6
7#include "base/metrics/histogram.h"
8#include "chrome/browser/drive/drive_notification_observer.h"
9#include "chrome/browser/invalidation/invalidation_service.h"
10#include "chrome/browser/invalidation/invalidation_service_factory.h"
11#include "google/cacheinvalidation/types.pb.h"
12#include "sync/notifier/object_id_invalidation_map.h"
13
14namespace drive {
15
16namespace {
17
18// The polling interval time is used when XMPP is disabled.
19const int kFastPollingIntervalInSecs = 60;
20
21// The polling interval time is used when XMPP is enabled.  Theoretically
22// polling should be unnecessary if XMPP is enabled, but just in case.
23const int kSlowPollingIntervalInSecs = 300;
24
25// The sync invalidation object ID for Google Drive.
26const char kDriveInvalidationObjectId[] = "CHANGELOG";
27
28}  // namespace
29
30DriveNotificationManager::DriveNotificationManager(
31    invalidation::InvalidationService* invalidation_service)
32    : invalidation_service_(invalidation_service),
33      push_notification_registered_(false),
34      push_notification_enabled_(false),
35      observers_notified_(false),
36      polling_timer_(true /* retain_user_task */, false /* is_repeating */),
37      weak_ptr_factory_(this) {
38  DCHECK(invalidation_service_);
39  RegisterDriveNotifications();
40  RestartPollingTimer();
41}
42
43DriveNotificationManager::~DriveNotificationManager() {}
44
45void DriveNotificationManager::Shutdown() {
46  // Unregister for Drive notifications.
47  if (!invalidation_service_ || !push_notification_registered_)
48    return;
49
50  // We unregister the handler without updating unregistering our IDs on
51  // purpose.  See the class comment on the InvalidationService interface for
52  // more information.
53  invalidation_service_->UnregisterInvalidationHandler(this);
54  invalidation_service_ = NULL;
55}
56
57void DriveNotificationManager::OnInvalidatorStateChange(
58    syncer::InvalidatorState state) {
59  push_notification_enabled_ = (state == syncer::INVALIDATIONS_ENABLED);
60  if (push_notification_enabled_) {
61    DVLOG(1) << "XMPP Notifications enabled";
62  } else {
63    DVLOG(1) << "XMPP Notifications disabled (state=" << state << ")";
64  }
65  FOR_EACH_OBSERVER(DriveNotificationObserver, observers_,
66                    OnPushNotificationEnabled(push_notification_enabled_));
67}
68
69void DriveNotificationManager::OnIncomingInvalidation(
70    const syncer::ObjectIdInvalidationMap& invalidation_map) {
71  DVLOG(2) << "XMPP Drive Notification Received";
72  syncer::ObjectIdSet ids = invalidation_map.GetObjectIds();
73  DCHECK_EQ(1U, ids.size());
74  const invalidation::ObjectId object_id(
75      ipc::invalidation::ObjectSource::COSMO_CHANGELOG,
76      kDriveInvalidationObjectId);
77  DCHECK_EQ(1U, ids.count(object_id));
78
79  // This effectively disables 'local acks'.  It tells the invalidations system
80  // to not bother saving invalidations across restarts for us.
81  // See crbug.com/320878.
82  invalidation_map.AcknowledgeAll();
83  NotifyObserversToUpdate(NOTIFICATION_XMPP);
84}
85
86void DriveNotificationManager::AddObserver(
87    DriveNotificationObserver* observer) {
88  observers_.AddObserver(observer);
89}
90
91void DriveNotificationManager::RemoveObserver(
92    DriveNotificationObserver* observer) {
93  observers_.RemoveObserver(observer);
94}
95
96void DriveNotificationManager::RestartPollingTimer() {
97  const int interval_secs = (push_notification_enabled_ ?
98                             kSlowPollingIntervalInSecs :
99                             kFastPollingIntervalInSecs);
100  polling_timer_.Stop();
101  polling_timer_.Start(
102      FROM_HERE,
103      base::TimeDelta::FromSeconds(interval_secs),
104      base::Bind(&DriveNotificationManager::NotifyObserversToUpdate,
105                 weak_ptr_factory_.GetWeakPtr(),
106                 NOTIFICATION_POLLING));
107}
108
109void DriveNotificationManager::NotifyObserversToUpdate(
110    NotificationSource source) {
111  DVLOG(1) << "Notifying observers: " << NotificationSourceToString(source);
112  FOR_EACH_OBSERVER(DriveNotificationObserver, observers_,
113                    OnNotificationReceived());
114  if (!observers_notified_) {
115    UMA_HISTOGRAM_BOOLEAN("Drive.PushNotificationInitiallyEnabled",
116                          push_notification_enabled_);
117  }
118  observers_notified_ = true;
119
120  // Note that polling_timer_ is not a repeating timer. Restarting manually
121  // here is better as XMPP may be received right before the polling timer is
122  // fired (i.e. we don't notify observers twice in a row).
123  RestartPollingTimer();
124}
125
126void DriveNotificationManager::RegisterDriveNotifications() {
127  DCHECK(!push_notification_enabled_);
128
129  if (!invalidation_service_)
130    return;
131
132  invalidation_service_->RegisterInvalidationHandler(this);
133  syncer::ObjectIdSet ids;
134  ids.insert(invalidation::ObjectId(
135      ipc::invalidation::ObjectSource::COSMO_CHANGELOG,
136      kDriveInvalidationObjectId));
137  invalidation_service_->UpdateRegisteredInvalidationIds(this, ids);
138  push_notification_registered_ = true;
139  OnInvalidatorStateChange(invalidation_service_->GetInvalidatorState());
140
141  UMA_HISTOGRAM_BOOLEAN("Drive.PushNotificationRegistered",
142                        push_notification_registered_);
143}
144
145// static
146std::string DriveNotificationManager::NotificationSourceToString(
147    NotificationSource source) {
148  switch (source) {
149    case NOTIFICATION_XMPP:
150      return "NOTIFICATION_XMPP";
151    case NOTIFICATION_POLLING:
152      return "NOTIFICATION_POLLING";
153  }
154
155  NOTREACHED();
156  return "";
157}
158
159}  // namespace drive
160