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