• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <set>
12 #include <string>
13 #include <vector>
14 
15 #include "base/command_line.h"
16 #include "base/guid.h"
17 #include "base/metrics/histogram.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "chrome/browser/extensions/api/synced_notifications_private/synced_notifications_shim.h"
22 #include "chrome/browser/notifications/desktop_notification_service.h"
23 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
24 #include "chrome/browser/notifications/notification.h"
25 #include "chrome/browser/notifications/notification_ui_manager.h"
26 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_delegate.h"
27 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
28 #include "chrome/browser/notifications/sync_notifier/synced_notification_app_info.h"
29 #include "chrome/browser/notifications/sync_notifier/synced_notification_app_info_service.h"
30 #include "chrome/browser/notifications/sync_notifier/synced_notification_app_info_service_factory.h"
31 #include "chrome/browser/notifications/sync_notifier/welcome_delegate.h"
32 #include "chrome/browser/profiles/profile.h"
33 #include "chrome/common/pref_names.h"
34 #include "components/pref_registry/pref_registry_syncable.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/user_metrics.h"
37 #include "extensions/browser/event_router.h"
38 #include "grit/generated_resources.h"
39 #include "grit/theme_resources.h"
40 #include "sync/api/sync_change.h"
41 #include "sync/api/sync_change_processor.h"
42 #include "sync/api/sync_error_factory.h"
43 #include "sync/protocol/sync.pb.h"
44 #include "sync/protocol/synced_notification_specifics.pb.h"
45 #include "third_party/WebKit/public/web/WebTextDirection.h"
46 #include "ui/base/l10n/l10n_util.h"
47 #include "ui/base/resource/resource_bundle.h"
48 #include "ui/message_center/notifier_settings.h"
49 #include "url/gurl.h"
50 
51 using base::UserMetricsAction;
52 
53 namespace notifier {
54 const char kFirstSyncedNotificationServiceId[] = "Google+";
55 const char kSyncedNotificationsWelcomeOrigin[] =
56     "synced-notifications://welcome";
57 
58 bool ChromeNotifierService::avoid_bitmap_fetching_for_test_ = false;
59 
ChromeNotifierService(Profile * profile,NotificationUIManager * manager)60 ChromeNotifierService::ChromeNotifierService(Profile* profile,
61                                              NotificationUIManager* manager)
62     : profile_(profile),
63       notification_manager_(manager),
64       synced_notification_first_run_(false),
65       weak_ptr_factory_(this) {
66   synced_notifications_shim_.reset(new SyncedNotificationsShim(
67       base::Bind(&ChromeNotifierService::FireSyncJSEvent,
68                  weak_ptr_factory_.GetWeakPtr())));
69 
70   InitializePrefs();
71 
72   // Get a pointer to the app info service so we can get app info data.
73   synced_notification_app_info_service_ =
74       SyncedNotificationAppInfoServiceFactory::GetForProfile(
75           profile_, Profile::IMPLICIT_ACCESS);
76 
77   DCHECK(synced_notification_app_info_service_ != NULL);
78 
79   synced_notification_app_info_service_->set_chrome_notifier_service(this);
80 }
81 
~ChromeNotifierService()82 ChromeNotifierService::~ChromeNotifierService() {
83   if (synced_notification_app_info_service_)
84     synced_notification_app_info_service_->set_chrome_notifier_service(NULL);
85 }
86 
87 // Methods from KeyedService.
Shutdown()88 void ChromeNotifierService::Shutdown() {}
89 
GetSyncedNotificationsShim()90 SyncedNotificationsShim* ChromeNotifierService::GetSyncedNotificationsShim() {
91   return synced_notifications_shim_.get();
92 }
93 
94 // syncer::SyncableService implementation.
95 
96 // This is called at startup to sync with the server.
97 // This code is not thread safe.
MergeDataAndStartSyncing(syncer::ModelType type,const syncer::SyncDataList & initial_sync_data,scoped_ptr<syncer::SyncChangeProcessor> sync_processor,scoped_ptr<syncer::SyncErrorFactory> error_handler)98 syncer::SyncMergeResult ChromeNotifierService::MergeDataAndStartSyncing(
99     syncer::ModelType type,
100     const syncer::SyncDataList& initial_sync_data,
101     scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
102     scoped_ptr<syncer::SyncErrorFactory> error_handler) {
103   thread_checker_.CalledOnValidThread();
104   DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type);
105   syncer::SyncMergeResult merge_result(syncer::SYNCED_NOTIFICATIONS);
106   // A list of local changes to send up to the sync server.
107   syncer::SyncChangeList new_changes;
108   sync_processor_ = sync_processor.Pass();
109 
110   for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin();
111        it != initial_sync_data.end(); ++it) {
112     const syncer::SyncData& sync_data = *it;
113     DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, sync_data.GetDataType());
114 
115     // Build a local notification object from the sync data.
116     scoped_ptr<SyncedNotification> incoming(CreateNotificationFromSyncData(
117         sync_data));
118     if (!incoming) {
119       NOTREACHED();
120       continue;
121     }
122 
123     // Process each incoming remote notification.
124     const std::string& key = incoming->GetKey();
125     DCHECK_GT(key.length(), 0U);
126     SyncedNotification* found = FindNotificationById(key);
127 
128     if (NULL == found) {
129       // If there are no conflicts, copy in the data from remote.
130       Add(incoming.Pass());
131     } else {
132       // If the incoming (remote) and stored (local) notifications match
133       // in all fields, we don't need to do anything here.
134       if (incoming->EqualsIgnoringReadState(*found)) {
135 
136         if (incoming->GetReadState() == found->GetReadState()) {
137           // Notification matches on the client and the server, nothing to do.
138           continue;
139         } else {
140           // If the read state is different, read wins for both places.
141           if (incoming->GetReadState() == SyncedNotification::kDismissed) {
142             // If it is marked as read on the server, but not the client.
143             found->NotificationHasBeenDismissed();
144             // Tell the Notification UI Manager to remove it.
145             notification_manager_->CancelById(found->GetKey());
146           } else if (incoming->GetReadState() == SyncedNotification::kRead) {
147             // If it is marked as read on the server, but not the client.
148             found->NotificationHasBeenRead();
149             // Tell the Notification UI Manager to remove it.
150             notification_manager_->CancelById(found->GetKey());
151           } else {
152             // If it is marked as read on the client, but not the server.
153             syncer::SyncData sync_data = CreateSyncDataFromNotification(*found);
154             new_changes.push_back(
155                 syncer::SyncChange(FROM_HERE,
156                                    syncer::SyncChange::ACTION_UPDATE,
157                                    sync_data));
158           }
159           // If local state changed, notify Notification UI Manager.
160         }
161       } else {
162         // If different, just replace the local with the remote.
163         // TODO(petewil): Someday we may allow changes from the client to
164         // flow upwards, when we do, we will need better merge resolution.
165         found->Update(sync_data);
166 
167         // Tell the notification manager to update the notification.
168         UpdateInMessageCenter(found);
169       }
170     }
171   }
172 
173   // Send up the changes that were made locally.
174   if (new_changes.size() > 0) {
175     merge_result.set_error(sync_processor_->ProcessSyncChanges(
176         FROM_HERE, new_changes));
177   }
178 
179   // Once we complete our first sync, we mark "first run" as false,
180   // subsequent runs of Synced Notifications will get normal treatment.
181   if (synced_notification_first_run_) {
182     synced_notification_first_run_ = false;
183     profile_->GetPrefs()->SetBoolean(prefs::kSyncedNotificationFirstRun, false);
184   }
185 
186   return merge_result;
187 }
188 
StopSyncing(syncer::ModelType type)189 void ChromeNotifierService::StopSyncing(syncer::ModelType type) {
190   DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type);
191   // Since this data type is not user-unselectable, we chose not to implement
192   // the stop syncing method, and instead do nothing here.
193 }
194 
GetAllSyncData(syncer::ModelType type) const195 syncer::SyncDataList ChromeNotifierService::GetAllSyncData(
196     syncer::ModelType type) const {
197   DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type);
198   syncer::SyncDataList sync_data;
199 
200   // Copy our native format data into a SyncDataList format.
201   ScopedVector<SyncedNotification>::const_iterator it =
202       notification_data_.begin();
203   for (; it != notification_data_.end(); ++it) {
204     sync_data.push_back(CreateSyncDataFromNotification(**it));
205   }
206 
207   return sync_data;
208 }
209 
210 // This method is called when there is an incoming sync change from the server.
ProcessSyncChanges(const tracked_objects::Location & from_here,const syncer::SyncChangeList & change_list)211 syncer::SyncError ChromeNotifierService::ProcessSyncChanges(
212     const tracked_objects::Location& from_here,
213     const syncer::SyncChangeList& change_list) {
214   thread_checker_.CalledOnValidThread();
215   syncer::SyncError error;
216 
217   for (syncer::SyncChangeList::const_iterator it = change_list.begin();
218        it != change_list.end(); ++it) {
219     syncer::SyncData sync_data = it->sync_data();
220     DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, sync_data.GetDataType());
221     syncer::SyncChange::SyncChangeType change_type = it->change_type();
222 
223     scoped_ptr<SyncedNotification> new_notification(
224         CreateNotificationFromSyncData(sync_data));
225     if (!new_notification.get()) {
226       NOTREACHED() << "Failed to read notification.";
227       continue;
228     }
229 
230     const std::string& key = new_notification->GetKey();
231     DCHECK_GT(key.length(), 0U);
232     SyncedNotification* found = FindNotificationById(key);
233 
234     switch (change_type) {
235       case syncer::SyncChange::ACTION_ADD:
236         // Intentional fall through, cases are identical.
237       case syncer::SyncChange::ACTION_UPDATE:
238         if (found == NULL) {
239           Add(new_notification.Pass());
240           break;
241         }
242         // Update it in our store.
243         found->Update(sync_data);
244         // Tell the notification manager to update the notification.
245         UpdateInMessageCenter(found);
246         break;
247 
248       case syncer::SyncChange::ACTION_DELETE:
249         if (found == NULL) {
250           break;
251         }
252         // Remove it from our store.
253         FreeNotificationById(key);
254         // Remove it from the message center.
255         UpdateInMessageCenter(new_notification.get());
256         // TODO(petewil): Do I need to remember that it was deleted in case the
257         // add arrives after the delete?  If so, how long do I need to remember?
258         break;
259 
260       default:
261         NOTREACHED();
262         break;
263     }
264   }
265 
266   return error;
267 }
268 
269 // Support functions for data type conversion.
270 
271 // Static method.  Get to the sync data in our internal format.
CreateSyncDataFromNotification(const SyncedNotification & notification)272 syncer::SyncData ChromeNotifierService::CreateSyncDataFromNotification(
273     const SyncedNotification& notification) {
274   // Construct the sync_data using the specifics from the notification.
275   return syncer::SyncData::CreateLocalData(
276       notification.GetKey(), notification.GetKey(),
277       notification.GetEntitySpecifics());
278 }
279 
280 // Convert from SyncData to our internal format.
281 scoped_ptr<SyncedNotification>
CreateNotificationFromSyncData(const syncer::SyncData & sync_data)282 ChromeNotifierService::CreateNotificationFromSyncData(
283     const syncer::SyncData& sync_data) {
284   // Get a pointer to our data within the sync_data object.
285   sync_pb::SyncedNotificationSpecifics specifics =
286       sync_data.GetSpecifics().synced_notification();
287 
288   // Check for mandatory fields in the sync_data object.
289   if (!specifics.has_coalesced_notification() ||
290       !specifics.coalesced_notification().has_key() ||
291       !specifics.coalesced_notification().has_read_state()) {
292     DVLOG(1) << "Synced Notification missing mandatory fields "
293              << "has coalesced notification? "
294              << specifics.has_coalesced_notification()
295              << " has key? " << specifics.coalesced_notification().has_key()
296              << " has read state? "
297              << specifics.coalesced_notification().has_read_state();
298     return scoped_ptr<SyncedNotification>();
299   }
300 
301   bool is_well_formed_unread_notification =
302       (static_cast<SyncedNotification::ReadState>(
303           specifics.coalesced_notification().read_state()) ==
304        SyncedNotification::kUnread &&
305        specifics.coalesced_notification().has_render_info());
306   bool is_well_formed_read_notification =
307       (static_cast<SyncedNotification::ReadState>(
308           specifics.coalesced_notification().read_state()) ==
309        SyncedNotification::kRead);
310   bool is_well_formed_dismissed_notification =
311       (static_cast<SyncedNotification::ReadState>(
312           specifics.coalesced_notification().read_state()) ==
313        SyncedNotification::kDismissed);
314 
315   // If the notification is poorly formed, return a null pointer.
316   if (!is_well_formed_unread_notification &&
317       !is_well_formed_read_notification &&
318       !is_well_formed_dismissed_notification) {
319     DVLOG(1) << "Synced Notification is not well formed."
320              << " unread well formed? "
321              << is_well_formed_unread_notification
322              << " dismissed well formed? "
323              << is_well_formed_dismissed_notification
324              << " read well formed? "
325              << is_well_formed_read_notification;
326     return scoped_ptr<SyncedNotification>();
327   }
328 
329   // Create a new notification object based on the supplied sync_data.
330   scoped_ptr<SyncedNotification> notification(
331       new SyncedNotification(sync_data, this, notification_manager_));
332 
333   return notification.Pass();
334 }
335 
336 // This returns a pointer into a vector that we own.  Caller must not free it.
337 // Returns NULL if no match is found.
FindNotificationById(const std::string & notification_id)338 SyncedNotification* ChromeNotifierService::FindNotificationById(
339     const std::string& notification_id) {
340   // TODO(petewil): We can make a performance trade off here.
341   // While the vector has good locality of reference, a map has faster lookup.
342   // Based on how big we expect this to get, maybe change this to a map.
343   ScopedVector<SyncedNotification>::const_iterator it =
344       notification_data_.begin();
345   for (; it != notification_data_.end(); ++it) {
346     SyncedNotification* notification = *it;
347     if (notification_id == notification->GetKey())
348       return *it;
349   }
350 
351   return NULL;
352 }
353 
FreeNotificationById(const std::string & notification_id)354 void ChromeNotifierService::FreeNotificationById(
355     const std::string& notification_id) {
356   ScopedVector<SyncedNotification>::iterator it = notification_data_.begin();
357   for (; it != notification_data_.end(); ++it) {
358     SyncedNotification* notification = *it;
359     if (notification_id == notification->GetKey()) {
360       notification_data_.erase(it);
361       return;
362     }
363   }
364 }
365 
GetSyncedNotificationServices(std::vector<message_center::Notifier * > * notifiers)366 void ChromeNotifierService::GetSyncedNotificationServices(
367     std::vector<message_center::Notifier*>* notifiers) {
368   // TODO(mukai|petewil): Check the profile's eligibility before adding the
369   // sample app.
370   std::vector<SyncedNotificationSendingServiceSettingsData>
371       sending_service_settings_data;
372 
373   if (synced_notification_app_info_service_ == NULL)
374     return;
375 
376   sending_service_settings_data =
377       synced_notification_app_info_service_->GetAllSendingServiceSettingsData();
378 
379   for (size_t ii = 0; ii < sending_service_settings_data.size(); ++ii) {
380 
381     // Enable or disable the sending service per saved preferences.
382     bool app_enabled = false;
383     std::set<std::string>::iterator iter;
384     iter = find(enabled_sending_services_.begin(),
385                 enabled_sending_services_.end(),
386                 sending_service_settings_data[ii].notifier_id.id);
387     app_enabled = iter != enabled_sending_services_.end();
388 
389     message_center::Notifier* app_info_notifier = new message_center::Notifier(
390         sending_service_settings_data[ii].notifier_id,
391         base::UTF8ToUTF16(
392             sending_service_settings_data[ii].settings_display_name),
393         app_enabled);
394 
395     app_info_notifier->icon = sending_service_settings_data[ii].settings_icon;
396 
397     // |notifiers| takes ownership of |app_info_notifier|.
398     notifiers->push_back(app_info_notifier);
399   }
400 }
401 
OnAddedAppIds(std::vector<std::string> added_app_ids)402 void ChromeNotifierService::OnAddedAppIds(
403     std::vector<std::string> added_app_ids) {
404 
405   std::vector<std::string>::const_iterator app_id_iter;
406   for (app_id_iter = added_app_ids.begin(); app_id_iter != added_app_ids.end();
407        ++app_id_iter) {
408     // Make sure this is not a dup, if it is, do nothing.
409     // TODO(petewil): consider a case insensitive compare.
410     std::set<std::string>::iterator sending_service_iter;
411     sending_service_iter = enabled_sending_services_.find(*app_id_iter);
412     if (sending_service_iter != enabled_sending_services_.end())
413       continue;
414 
415     // Find any newly enabled notifications and call display on them.
416     // Show the welcome toast if required.
417     ScopedVector<SyncedNotification>::iterator notification_iter;
418     for (notification_iter = notification_data_.begin();
419          notification_iter != notification_data_.end();
420          ++notification_iter) {
421       (*notification_iter)->ShowAllForAppId(profile_, *app_id_iter);
422     }
423   }
424 }
425 
OnRemovedAppIds(std::vector<std::string> removed_app_ids)426 void ChromeNotifierService::OnRemovedAppIds(
427     std::vector<std::string> removed_app_ids) {
428   // Remove from enabled sending services.
429   // Don't remove from initialized sending services. If it gets re-added later,
430   // we want to remember the user's decision, so we also leave prefs intact.
431 
432   // Find any displayed notifications and remove them from the notification
433   // center.
434   std::vector<std::string>::const_iterator app_id_iter;
435   for (app_id_iter = removed_app_ids.begin();
436        app_id_iter != removed_app_ids.end();
437        ++app_id_iter) {
438     // Find any newly disabled notifications and remove them.
439     ScopedVector<SyncedNotification>::iterator notification_iter;
440     for (notification_iter = notification_data_.begin();
441          notification_iter != notification_data_.end();
442          ++notification_iter) {
443       (*notification_iter)->HideAllForAppId(*app_id_iter);
444     }
445   }
446 }
447 
MarkNotificationAsRead(const std::string & key)448 void ChromeNotifierService::MarkNotificationAsRead(
449     const std::string& key) {
450   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
451   SyncedNotification* notification = FindNotificationById(key);
452   CHECK(notification != NULL);
453 
454   notification->NotificationHasBeenRead();
455   syncer::SyncChangeList new_changes;
456 
457   syncer::SyncData sync_data = CreateSyncDataFromNotification(*notification);
458   new_changes.push_back(
459       syncer::SyncChange(FROM_HERE,
460                          syncer::SyncChange::ACTION_UPDATE,
461                          sync_data));
462 
463   // Send up the changes that were made locally.
464   sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes);
465 }
466 
FireSyncJSEvent(scoped_ptr<extensions::Event> event)467 void ChromeNotifierService::FireSyncJSEvent(
468     scoped_ptr<extensions::Event> event) {
469   event->restrict_to_browser_context = profile_;
470   // TODO(synced notifications): consider broadcasting to a specific extension
471   // id.
472   extensions::EventRouter::Get(profile_)->BroadcastEvent(event.Pass());
473 }
474 
475 // Add a new notification to our data structure.  This takes ownership
476 // of the passed in pointer.
Add(scoped_ptr<SyncedNotification> notification)477 void ChromeNotifierService::Add(scoped_ptr<SyncedNotification> notification) {
478   SyncedNotification* notification_copy = notification.get();
479   // Take ownership of the object and put it into our local storage.
480   notification_data_.push_back(notification.release());
481 
482   // If the user is not interested in this type of notification, ignore it.
483   std::string sending_service_id = GetSendingServiceId(notification_copy);
484   std::set<std::string>::iterator iter = find(enabled_sending_services_.begin(),
485                                               enabled_sending_services_.end(),
486                                               sending_service_id);
487   if (iter == enabled_sending_services_.end()) {
488     iter = find(initialized_sending_services_.begin(),
489                 initialized_sending_services_.end(),
490                 sending_service_id);
491     if (iter != initialized_sending_services_.end())
492       return;
493   }
494 
495   UpdateInMessageCenter(notification_copy);
496 }
497 
GetSendingServiceId(const SyncedNotification * synced_notification)498 std::string ChromeNotifierService::GetSendingServiceId(
499     const SyncedNotification* synced_notification) {
500   // Get the App Id from the notification, and look it up in the synced
501   // notification app info service.
502   std::string app_id = synced_notification->GetAppId();
503 
504   DCHECK(synced_notification_app_info_service_ != NULL);
505 
506   return synced_notification_app_info_service_->FindSendingServiceNameFromAppId(
507       app_id);
508 }
509 
AddForTest(scoped_ptr<notifier::SyncedNotification> notification)510 void ChromeNotifierService::AddForTest(
511     scoped_ptr<notifier::SyncedNotification> notification) {
512   notification_data_.push_back(notification.release());
513 }
514 
SetSyncedNotificationAppInfoServiceForTest(SyncedNotificationAppInfoService * synced_notification_app_info_service)515 void ChromeNotifierService::SetSyncedNotificationAppInfoServiceForTest(
516     SyncedNotificationAppInfoService* synced_notification_app_info_service) {
517   // If there already is a service attached, clear their reference to us.
518   if (synced_notification_app_info_service_)
519     synced_notification_app_info_service_->set_chrome_notifier_service(NULL);
520 
521   synced_notification_app_info_service_ = synced_notification_app_info_service;
522   synced_notification_app_info_service_->set_chrome_notifier_service(this);
523 }
524 
UpdateInMessageCenter(SyncedNotification * notification)525 void ChromeNotifierService::UpdateInMessageCenter(
526     SyncedNotification* notification) {
527   // If the feature is disabled, exit now.
528   if (!notifier::ChromeNotifierServiceFactory::UseSyncedNotifications(
529       CommandLine::ForCurrentProcess()))
530     return;
531 
532   notification->LogNotification();
533 
534   if (notification->GetReadState() == SyncedNotification::kUnread) {
535     // If the message is unread, update it.
536     Display(notification);
537   } else {
538     // If the message is read or deleted, dismiss it from the center.
539     // We intentionally ignore errors if it is not in the center.
540     notification_manager_->CancelById(notification->GetKey());
541   }
542 }
543 
Display(SyncedNotification * notification)544 void ChromeNotifierService::Display(SyncedNotification* notification) {
545   // If this is the first run for the feature, don't surprise the user.
546   // Instead, place all backlogged notifications into the notification
547   // center.
548   if (synced_notification_first_run_) {
549     // Setting the toast state to false will prevent toasting the notification.
550     notification->set_toast_state(false);
551   }
552 
553   // Our tests cannot use the network for reliability reasons.
554   if (avoid_bitmap_fetching_for_test_) {
555     notification->Show(profile_);
556     return;
557   }
558 
559   // Set up to fetch the bitmaps.
560   notification->QueueBitmapFetchJobs(this, profile_);
561 
562   // Start the bitmap fetching, Show() will be called when the last bitmap
563   // either arrives or times out.
564   notification->StartBitmapFetch();
565 }
566 
OnSyncedNotificationServiceEnabled(const std::string & notifier_id,bool enabled)567 void ChromeNotifierService::OnSyncedNotificationServiceEnabled(
568     const std::string& notifier_id, bool enabled) {
569   std::set<std::string>::iterator iter;
570 
571   // Make a copy of the notifier_id, which might not have lifetime long enough
572   // for this function to finish all of its work.
573   std::string notifier_id_copy(notifier_id);
574 
575   iter = find(enabled_sending_services_.begin(),
576               enabled_sending_services_.end(),
577               notifier_id_copy);
578 
579   base::ListValue synced_notification_services;
580 
581   // Add the notifier_id if it is enabled and not already there.
582   if (iter == enabled_sending_services_.end() && enabled) {
583     enabled_sending_services_.insert(notifier_id_copy);
584     // Check now for any outstanding notifications.
585     DisplayUnreadNotificationsFromSource(notifier_id);
586     BuildServiceListValueInplace(enabled_sending_services_,
587                                  &synced_notification_services);
588     // Add this preference to the enabled list.
589     profile_->GetPrefs()->Set(prefs::kEnabledSyncedNotificationSendingServices,
590                               synced_notification_services);
591     // Remove the notifier_id if it is disabled and present.
592   } else if (iter != enabled_sending_services_.end() && !enabled) {
593     enabled_sending_services_.erase(iter);
594     BuildServiceListValueInplace(enabled_sending_services_,
595                                  &synced_notification_services);
596     // Remove this peference from the enabled list.
597     profile_->GetPrefs()->Set(prefs::kEnabledSyncedNotificationSendingServices,
598                               synced_notification_services);
599     RemoveUnreadNotificationsFromSource(notifier_id_copy);
600   }
601 
602   // Collect UMA statistics when a service is enabled or disabled.
603   if (enabled) {
604     content::RecordAction(
605         UserMetricsAction("SyncedNotifications.SendingServiceEnabled"));
606   } else {
607     content::RecordAction(
608         UserMetricsAction("SyncedNotifications.SendingServiceDisabled"));
609   }
610 
611   // Collect individual service enabling/disabling statistics.
612   CollectPerServiceEnablingStatistics(notifier_id, enabled);
613 
614   return;
615 }
616 
CollectPerServiceEnablingStatistics(const std::string & notifier_id,bool enabled)617 void ChromeNotifierService::CollectPerServiceEnablingStatistics(
618     const std::string& notifier_id,
619     bool enabled) {
620   // TODO(petewil) - This approach does not scale well as we add new services,
621   // but we are limited to using predefined ENUM values in histogram based UMA
622   // data, which does not permit arbitrary strings.
623   // Find a way to make it scale, or remove enum value this when we have enough
624   // data.
625 
626   ChromeNotifierServiceActionType action =
627       CHROME_NOTIFIER_SERVICE_ACTION_UNKNOWN;
628 
629   // Derive action type from notifier_id and enabled.
630   // TODO(petewil): Add more sending services as they are enabled.
631   if (notifier_id == std::string(kFirstSyncedNotificationServiceId)) {
632     action = enabled
633         ? CHROME_NOTIFIER_SERVICE_ACTION_FIRST_SERVICE_ENABLED
634         : CHROME_NOTIFIER_SERVICE_ACTION_FIRST_SERVICE_DISABLED;
635   }
636 
637   UMA_HISTOGRAM_ENUMERATION("ChromeNotifierService.Actions",
638                             action,
639                             CHROME_NOTIFIER_SERVICE_ACTION_COUNT);
640 }
641 
BuildServiceListValueInplace(std::set<std::string> services,base::ListValue * list_value)642 void ChromeNotifierService::BuildServiceListValueInplace(
643     std::set<std::string> services, base::ListValue* list_value) {
644   std::set<std::string>::iterator iter;
645 
646   // Iterate over the strings, adding each one to the list value
647   for (iter = services.begin();
648        iter != services.end();
649        ++iter) {
650     base::StringValue* string_value(new base::StringValue(*iter));
651     list_value->Append(string_value);
652   }
653 }
654 
DisplayUnreadNotificationsFromSource(const std::string & notifier_id)655 void ChromeNotifierService::DisplayUnreadNotificationsFromSource(
656     const std::string& notifier_id) {
657   for (std::vector<SyncedNotification*>::const_iterator iter =
658           notification_data_.begin();
659        iter != notification_data_.end();
660        ++iter) {
661     if (GetSendingServiceId(*iter) == notifier_id &&
662         (*iter)->GetReadState() == SyncedNotification::kUnread)
663       Display(*iter);
664   }
665 }
666 
RemoveUnreadNotificationsFromSource(const std::string & notifier_id)667 void ChromeNotifierService::RemoveUnreadNotificationsFromSource(
668     const std::string& notifier_id) {
669   for (std::vector<SyncedNotification*>::const_iterator iter =
670           notification_data_.begin();
671        iter != notification_data_.end();
672        ++iter) {
673     if (GetSendingServiceId(*iter) == notifier_id &&
674         (*iter)->GetReadState() == SyncedNotification::kUnread) {
675       notification_manager_->CancelById((*iter)->GetKey());
676     }
677   }
678 }
679 
OnEnabledSendingServiceListPrefChanged(std::set<std::string> * ids_field)680 void ChromeNotifierService::OnEnabledSendingServiceListPrefChanged(
681     std::set<std::string>* ids_field) {
682   ids_field->clear();
683   const std::vector<std::string> pref_list =
684       enabled_sending_services_prefs_.GetValue();
685   for (size_t i = 0; i < pref_list.size(); ++i) {
686     std::string element = pref_list[i];
687     if (!element.empty())
688       ids_field->insert(element);
689     else
690       LOG(WARNING) << i << "-th element is not a string "
691                    << prefs::kEnabledSyncedNotificationSendingServices;
692   }
693 }
694 
OnInitializedSendingServiceListPrefChanged(std::set<std::string> * ids_field)695 void ChromeNotifierService::OnInitializedSendingServiceListPrefChanged(
696     std::set<std::string>* ids_field) {
697   ids_field->clear();
698   const std::vector<std::string> pref_list =
699       initialized_sending_services_prefs_.GetValue();
700   for (size_t i = 0; i < pref_list.size(); ++i) {
701     std::string element = pref_list[i];
702     if (!element.empty())
703       ids_field->insert(element);
704     else
705       LOG(WARNING) << i << "-th element is not a string for "
706                    << prefs::kInitializedSyncedNotificationSendingServices;
707   }
708 }
709 
OnSyncedNotificationFirstRunBooleanPrefChanged(bool * new_value)710 void ChromeNotifierService::OnSyncedNotificationFirstRunBooleanPrefChanged(
711     bool* new_value) {
712   synced_notification_first_run_ = *new_value;
713 }
714 
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)715 void ChromeNotifierService::RegisterProfilePrefs(
716     user_prefs::PrefRegistrySyncable* registry) {
717   // Register the pref for the list of enabled services.
718   registry->RegisterListPref(
719       prefs::kEnabledSyncedNotificationSendingServices,
720       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
721   // Register the pref for the list of initialized services.
722   registry->RegisterListPref(
723       prefs::kInitializedSyncedNotificationSendingServices,
724       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
725   // Register the preference for first run status, defaults to "true",
726   // meaning that this is the first run of the Synced Notification feature.
727   registry->RegisterBooleanPref(
728       prefs::kSyncedNotificationFirstRun, true,
729       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
730 }
731 
InitializePrefs()732 void ChromeNotifierService::InitializePrefs() {
733   // Set up any pref changes to update our list of services.
734   enabled_sending_services_prefs_.Init(
735       prefs::kEnabledSyncedNotificationSendingServices,
736       profile_->GetPrefs(),
737       base::Bind(
738           &ChromeNotifierService::OnEnabledSendingServiceListPrefChanged,
739           base::Unretained(this),
740           base::Unretained(&enabled_sending_services_)));
741   initialized_sending_services_prefs_.Init(
742       prefs::kInitializedSyncedNotificationSendingServices,
743       profile_->GetPrefs(),
744       base::Bind(
745           &ChromeNotifierService::OnInitializedSendingServiceListPrefChanged,
746           base::Unretained(this),
747           base::Unretained(&initialized_sending_services_)));
748   synced_notification_first_run_prefs_.Init(
749       prefs::kSyncedNotificationFirstRun,
750       profile_->GetPrefs(),
751       base::Bind(
752           &ChromeNotifierService::
753               OnSyncedNotificationFirstRunBooleanPrefChanged,
754           base::Unretained(this),
755           base::Unretained(&synced_notification_first_run_)));
756 
757   // Get the prefs from last session into our memeber varilables
758   OnEnabledSendingServiceListPrefChanged(&enabled_sending_services_);
759   OnInitializedSendingServiceListPrefChanged(&initialized_sending_services_);
760 
761   synced_notification_first_run_ =
762       profile_->GetPrefs()->GetBoolean(prefs::kSyncedNotificationFirstRun);
763 }
764 
ShowWelcomeToastIfNecessary(const SyncedNotification * synced_notification,NotificationUIManager * notification_ui_manager)765 void ChromeNotifierService::ShowWelcomeToastIfNecessary(
766     const SyncedNotification* synced_notification,
767     NotificationUIManager* notification_ui_manager) {
768   const std::string& sending_service_id =
769       GetSendingServiceId(synced_notification);
770 
771   std::set<std::string>::iterator iter;
772   iter = find(initialized_sending_services_.begin(),
773               initialized_sending_services_.end(),
774               sending_service_id);
775 
776   // If we already initialized the sending service, then return early since no
777   // welcome toast is necessary.
778   if (iter != initialized_sending_services_.end())
779     return;
780 
781   // If there is no app info, we can't show a welcome toast.  All synced
782   // notifications will be delayed until an app_info data structure can be
783   // constructed for them.
784   notifier::SyncedNotificationAppInfo* app_info =
785       FindAppInfoByAppId(synced_notification->GetAppId());
786   if (!app_info)
787     return;
788 
789   if (app_info->settings_icon_url().is_empty()) {
790     gfx::Image notification_app_icon = synced_notification->GetAppIcon();
791     if (notification_app_icon.IsEmpty()) {
792       // This block should only be reached in tests since the downloads are
793       // already finished for |synced_notification|.
794       DVLOG(1) << "Unable to find the app icon for the welcome notification. "
795                << "Service ID: " << sending_service_id;
796     }
797   }
798 
799   message_center::NotifierId notifier_id(
800       message_center::NotifierId::SYNCED_NOTIFICATION_SERVICE,
801       sending_service_id);
802 
803   Notification notification = CreateWelcomeNotificationForService(app_info);
804   notification_ui_manager->Add(notification, profile_);
805 
806   enabled_sending_services_.insert(sending_service_id);
807   initialized_sending_services_.insert(sending_service_id);
808 
809   // Build a ListValue with the list of services to be enabled.
810   base::ListValue enabled_sending_services;
811   base::ListValue initialized_sending_services;
812 
813   // Mark any new services as enabled in preferences.
814   BuildServiceListValueInplace(enabled_sending_services_,
815                                &enabled_sending_services);
816   profile_->GetPrefs()->Set(prefs::kEnabledSyncedNotificationSendingServices,
817                             enabled_sending_services);
818   // Mark any new services as initialized in preferences.
819   BuildServiceListValueInplace(initialized_sending_services_,
820                                &initialized_sending_services);
821   profile_->GetPrefs()->Set(
822       prefs::kInitializedSyncedNotificationSendingServices,
823       initialized_sending_services);
824 }
825 
FindAppInfoByAppId(const std::string & app_id) const826 notifier::SyncedNotificationAppInfo* ChromeNotifierService::FindAppInfoByAppId(
827     const std::string& app_id) const {
828   if (NULL == synced_notification_app_info_service_)
829     return NULL;
830 
831   return synced_notification_app_info_service_->
832       FindSyncedNotificationAppInfoByAppId(app_id);
833 }
834 
CreateWelcomeNotificationForService(SyncedNotificationAppInfo * app_info)835 const Notification ChromeNotifierService::CreateWelcomeNotificationForService(
836     SyncedNotificationAppInfo* app_info) {
837   std::string welcome_notification_id = base::GenerateGUID();
838   message_center::NotifierId notifier_id = app_info->GetNotifierId();
839   scoped_refptr<WelcomeDelegate> delegate(
840       new WelcomeDelegate(welcome_notification_id,
841                           profile_,
842                           notifier_id,
843                           app_info->welcome_link_url()));
844 
845   message_center::ButtonInfo button_info(
846       l10n_util::GetStringUTF16(IDS_NOTIFIER_WELCOME_BUTTON));
847   button_info.icon = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
848       IDR_NOTIFIER_BLOCK_BUTTON);
849 
850   message_center::RichNotificationData rich_notification_data;
851   rich_notification_data.buttons.push_back(button_info);
852   return Notification(
853       message_center::NOTIFICATION_TYPE_BASE_FORMAT,
854       GURL(kSyncedNotificationsWelcomeOrigin),
855       base::UTF8ToUTF16(app_info->settings_display_name()),
856       l10n_util::GetStringUTF16(IDS_NOTIFIER_WELCOME_BODY),
857       app_info->icon(),
858       blink::WebTextDirectionDefault,
859       notifier_id,
860       l10n_util::GetStringUTF16(IDS_NOTIFICATION_WELCOME_DISPLAY_SOURCE),
861       base::UTF8ToUTF16(welcome_notification_id),
862       rich_notification_data,
863       delegate.get());
864 }
865 
866 }  // namespace notifier
867