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