chrome_notifier_service.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
1// Copyright (c) 2012 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// The ChromeNotifierService works together with sync to maintain the state of
6// user notifications, which can then be presented in the notification center,
7// via the Notification UI Manager.
8
9#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
10
11#include <string>
12#include <vector>
13
14#include "chrome/browser/notifications/desktop_notification_service.h"
15#include "chrome/browser/notifications/desktop_notification_service_factory.h"
16#include "chrome/browser/notifications/notification.h"
17#include "chrome/browser/notifications/notification_ui_manager.h"
18#include "chrome/browser/profiles/profile.h"
19#include "content/public/browser/browser_thread.h"
20#include "grit/generated_resources.h"
21#include "grit/theme_resources.h"
22#include "sync/api/sync_change.h"
23#include "sync/api/sync_change_processor.h"
24#include "sync/api/sync_error_factory.h"
25#include "sync/protocol/sync.pb.h"
26#include "sync/protocol/synced_notification_specifics.pb.h"
27#include "third_party/WebKit/public/web/WebTextDirection.h"
28#include "ui/base/l10n/l10n_util.h"
29#include "ui/base/resource/resource_bundle.h"
30#include "ui/message_center/notifier_settings.h"
31#include "url/gurl.h"
32
33namespace notifier {
34namespace {
35
36const char kFirstSyncedNotificationServiceId[] = "Google+";
37
38}
39
40bool ChromeNotifierService::avoid_bitmap_fetching_for_test_ = false;
41
42ChromeNotifierService::ChromeNotifierService(Profile* profile,
43                                             NotificationUIManager* manager)
44    : profile_(profile), notification_manager_(manager) {
45}
46ChromeNotifierService::~ChromeNotifierService() {}
47
48// Methods from BrowserContextKeyedService.
49void ChromeNotifierService::Shutdown() {}
50
51// syncer::SyncableService implementation.
52
53// This is called at startup to sync with the server.
54// This code is not thread safe.
55syncer::SyncMergeResult ChromeNotifierService::MergeDataAndStartSyncing(
56      syncer::ModelType type,
57      const syncer::SyncDataList& initial_sync_data,
58      scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
59      scoped_ptr<syncer::SyncErrorFactory> error_handler) {
60  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
61  DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type);
62  syncer::SyncMergeResult merge_result(syncer::SYNCED_NOTIFICATIONS);
63  // A list of local changes to send up to the sync server.
64  syncer::SyncChangeList new_changes;
65  sync_processor_ = sync_processor.Pass();
66
67  for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin();
68       it != initial_sync_data.end(); ++it) {
69    const syncer::SyncData& sync_data = *it;
70    DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, sync_data.GetDataType());
71
72    // Build a local notification object from the sync data.
73    scoped_ptr<SyncedNotification> incoming(CreateNotificationFromSyncData(
74        sync_data));
75    if (!incoming) {
76      // TODO(petewil): Turn this into a NOTREACHED() call once we fix the
77      // underlying problem causing bad data.
78      LOG(WARNING) << "Badly formed sync data in incoming notification";
79      continue;
80    }
81
82    // Process each incoming remote notification.
83    const std::string& key = incoming->GetKey();
84    DCHECK_GT(key.length(), 0U);
85    SyncedNotification* found = FindNotificationById(key);
86
87    if (NULL == found) {
88      // If there are no conflicts, copy in the data from remote.
89      Add(incoming.Pass());
90    } else {
91      // If the incoming (remote) and stored (local) notifications match
92      // in all fields, we don't need to do anything here.
93      if (incoming->EqualsIgnoringReadState(*found)) {
94
95        if (incoming->GetReadState() == found->GetReadState()) {
96          // Notification matches on the client and the server, nothing to do.
97          continue;
98        } else  {
99          // If the read state is different, read wins for both places.
100          if (incoming->GetReadState() == SyncedNotification::kDismissed) {
101            // If it is marked as read on the server, but not the client.
102            found->NotificationHasBeenDismissed();
103            // Tell the Notification UI Manager to mark it read.
104            notification_manager_->CancelById(found->GetKey());
105          } else {
106            // If it is marked as read on the client, but not the server.
107            syncer::SyncData sync_data = CreateSyncDataFromNotification(*found);
108            new_changes.push_back(
109                syncer::SyncChange(FROM_HERE,
110                                   syncer::SyncChange::ACTION_UPDATE,
111                                   sync_data));
112          }
113          // If local state changed, notify Notification UI Manager.
114        }
115      // For any other conflict besides read state, treat it as an update.
116      } else {
117        // If different, just replace the local with the remote.
118        // TODO(petewil): Someday we may allow changes from the client to
119        // flow upwards, when we do, we will need better merge resolution.
120        found->Update(sync_data);
121
122        // Tell the notification manager to update the notification.
123        Display(found);
124      }
125    }
126  }
127
128  // Send up the changes that were made locally.
129  if (new_changes.size() > 0) {
130    merge_result.set_error(sync_processor_->ProcessSyncChanges(
131        FROM_HERE, new_changes));
132  }
133
134  return merge_result;
135}
136
137void ChromeNotifierService::StopSyncing(syncer::ModelType type) {
138  DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type);
139  // TODO(petewil): implement
140}
141
142syncer::SyncDataList ChromeNotifierService::GetAllSyncData(
143      syncer::ModelType type) const {
144  DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type);
145  syncer::SyncDataList sync_data;
146
147  // Copy our native format data into a SyncDataList format.
148  for (std::vector<SyncedNotification*>::const_iterator it =
149          notification_data_.begin();
150      it != notification_data_.end();
151      ++it) {
152    sync_data.push_back(CreateSyncDataFromNotification(**it));
153  }
154
155  return sync_data;
156}
157
158// This method is called when there is an incoming sync change from the server.
159syncer::SyncError ChromeNotifierService::ProcessSyncChanges(
160      const tracked_objects::Location& from_here,
161      const syncer::SyncChangeList& change_list) {
162  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
163  syncer::SyncError error;
164
165  for (syncer::SyncChangeList::const_iterator it = change_list.begin();
166       it != change_list.end(); ++it) {
167    syncer::SyncData sync_data = it->sync_data();
168    DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, sync_data.GetDataType());
169    syncer::SyncChange::SyncChangeType change_type = it->change_type();
170
171    scoped_ptr<SyncedNotification> new_notification(
172        CreateNotificationFromSyncData(sync_data));
173    if (!new_notification.get()) {
174      NOTREACHED() << "Failed to read notification.";
175      continue;
176    }
177
178    switch (change_type) {
179      case syncer::SyncChange::ACTION_ADD:
180        // TODO(petewil): Update the notification if it already exists
181        // as opposed to adding it.
182        Add(new_notification.Pass());
183        break;
184      // TODO(petewil): Implement code to add delete and update actions.
185
186      default:
187        break;
188    }
189  }
190
191  return error;
192}
193
194// Support functions for data type conversion.
195
196// Static method.  Get to the sync data in our internal format.
197syncer::SyncData ChromeNotifierService::CreateSyncDataFromNotification(
198    const SyncedNotification& notification) {
199  // Construct the sync_data using the specifics from the notification.
200  return syncer::SyncData::CreateLocalData(
201      notification.GetKey(), notification.GetKey(),
202      notification.GetEntitySpecifics());
203}
204
205// Static Method.  Convert from SyncData to our internal format.
206scoped_ptr<SyncedNotification>
207    ChromeNotifierService::CreateNotificationFromSyncData(
208        const syncer::SyncData& sync_data) {
209  // Get a pointer to our data within the sync_data object.
210  sync_pb::SyncedNotificationSpecifics specifics =
211      sync_data.GetSpecifics().synced_notification();
212
213  // Check for mandatory fields in the sync_data object.
214  if (!specifics.has_coalesced_notification() ||
215      !specifics.coalesced_notification().has_key() ||
216      !specifics.coalesced_notification().has_read_state()) {
217    DVLOG(1) << "Synced Notification missing mandatory fields "
218             << "has coalesced notification? "
219             << specifics.has_coalesced_notification()
220             << " has key? " << specifics.coalesced_notification().has_key()
221             << " has read state? "
222             << specifics.coalesced_notification().has_read_state();
223    return scoped_ptr<SyncedNotification>();
224  }
225
226  // TODO(petewil): Is this the right set?  Should I add more?
227  bool is_well_formed_unread_notification =
228      (static_cast<SyncedNotification::ReadState>(
229          specifics.coalesced_notification().read_state()) ==
230       SyncedNotification::kUnread &&
231       specifics.coalesced_notification().has_render_info());
232  bool is_well_formed_dismissed_notification =
233      (static_cast<SyncedNotification::ReadState>(
234          specifics.coalesced_notification().read_state()) ==
235       SyncedNotification::kDismissed);
236
237  // If the notification is poorly formed, return a null pointer.
238  if (!is_well_formed_unread_notification &&
239      !is_well_formed_dismissed_notification) {
240    DVLOG(1) << "Synced Notification is not well formed."
241             << " unread well formed? "
242             << is_well_formed_unread_notification
243             << " dismissed well formed? "
244             << is_well_formed_dismissed_notification;
245    return scoped_ptr<SyncedNotification>();
246  }
247
248  // Create a new notification object based on the supplied sync_data.
249  scoped_ptr<SyncedNotification> notification(
250      new SyncedNotification(sync_data));
251
252  return notification.Pass();
253}
254
255// This returns a pointer into a vector that we own.  Caller must not free it.
256// Returns NULL if no match is found.
257SyncedNotification* ChromeNotifierService::FindNotificationById(
258    const std::string& notification_id) {
259  // TODO(petewil): We can make a performance trade off here.
260  // While the vector has good locality of reference, a map has faster lookup.
261  // Based on how big we expect this to get, maybe change this to a map.
262  for (std::vector<SyncedNotification*>::const_iterator it =
263          notification_data_.begin();
264      it != notification_data_.end();
265      ++it) {
266    SyncedNotification* notification = *it;
267    if (notification_id == notification->GetKey())
268      return *it;
269  }
270
271  return NULL;
272}
273
274void ChromeNotifierService::GetSyncedNotificationServices(
275    std::vector<message_center::Notifier*>* notifiers) {
276  // TODO(mukai|petewil): Check the profile's eligibility before adding the
277  // sample app.
278
279  // TODO(petewil): Really obtain the list of synced notification sending
280  // services from the server and create the list of ids here.  Until then, we
281  // are hardcoding the service names.  Once that is done, remove this
282  // hardcoding.
283  // crbug.com/248337
284  DesktopNotificationService* desktop_notification_service =
285      DesktopNotificationServiceFactory::GetForProfile(profile_);
286  message_center::NotifierId notifier_id(
287      message_center::NotifierId::SYNCED_NOTIFICATION_SERVICE,
288      kFirstSyncedNotificationServiceId);
289  message_center::Notifier* notifier_service = new message_center::Notifier(
290      notifier_id,
291      l10n_util::GetStringUTF16(
292          IDS_FIRST_SYNCED_NOTIFICATION_SERVICE_NAME),
293      desktop_notification_service->IsNotifierEnabled(notifier_id));
294
295  // Add icons for our sending services.
296  // TODO(petewil): Replace this temporary hardcoding with a new sync datatype
297  // to dynamically get the name and icon for each synced notification sending
298  // service.  Until then, we use hardcoded service icons for all services.
299  // crbug.com/248337
300  notifier_service->icon = ui::ResourceBundle::GetSharedInstance().
301      GetImageNamed(IDR_TEMPORARY_GOOGLE_PLUS_ICON);
302
303  notifiers->push_back(notifier_service);
304}
305
306void ChromeNotifierService::MarkNotificationAsDismissed(
307    const std::string& key) {
308  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
309  SyncedNotification* notification = FindNotificationById(key);
310  CHECK(notification != NULL);
311
312  notification->NotificationHasBeenDismissed();
313  syncer::SyncChangeList new_changes;
314
315  syncer::SyncData sync_data = CreateSyncDataFromNotification(*notification);
316  new_changes.push_back(
317      syncer::SyncChange(FROM_HERE,
318                         syncer::SyncChange::ACTION_UPDATE,
319                         sync_data));
320
321  // Send up the changes that were made locally.
322  sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes);
323}
324
325// Add a new notification to our data structure.  This takes ownership
326// of the passed in pointer.
327void ChromeNotifierService::Add(scoped_ptr<SyncedNotification> notification) {
328  SyncedNotification* notification_copy = notification.get();
329  // Take ownership of the object and put it into our local storage.
330  notification_data_.push_back(notification.release());
331
332  // If the user is not interested in this type of notification, ignore it.
333  std::vector<std::string>::iterator iter =
334      find(enabled_sending_services_.begin(),
335           enabled_sending_services_.end(),
336           notification_copy->GetSendingServiceId());
337  if (iter == enabled_sending_services_.end()) {
338    return;
339  }
340
341  Display(notification_copy);
342}
343
344void ChromeNotifierService::AddForTest(
345    scoped_ptr<notifier::SyncedNotification> notification) {
346    notification_data_.push_back(notification.release());
347  }
348
349void ChromeNotifierService::Display(SyncedNotification* notification) {
350
351  // Set up to fetch the bitmaps.
352  notification->QueueBitmapFetchJobs(notification_manager_,
353                                          this,
354                                          profile_);
355
356  // Our tests cannot use the network for reliability reasons.
357  if (avoid_bitmap_fetching_for_test_) {
358    return;
359  }
360
361  // Start the bitmap fetching, Show() will be called when the last bitmap
362  // either arrives or times out.
363  notification->StartBitmapFetch();
364}
365
366void ChromeNotifierService::OnSyncedNotificationServiceEnabled(
367    const std::string& notifier_id, bool enabled) {
368  std::vector<std::string>::iterator iter;
369
370  iter = find(enabled_sending_services_.begin(),
371              enabled_sending_services_.end(),
372              notifier_id);
373
374  // Add the notifier_id if it is enabled and not already there.
375  if (iter == enabled_sending_services_.end() && enabled) {
376    enabled_sending_services_.push_back(notifier_id);
377    // TODO(petewil) Check now for any outstanding notifications.
378  // Remove the notifier_id if it is disabled and present.
379  } else if (iter != enabled_sending_services_.end() && !enabled) {
380    enabled_sending_services_.erase(iter);
381  }
382
383  // Otherwise, nothing to do, we can exit.
384  return;
385}
386
387}  // namespace notifier
388