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.
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.
9 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
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"
46 using base::UserMetricsAction;
49 const char kFirstSyncedNotificationServiceId[] = "Google+";
50 const char kSyncedNotificationsWelcomeOrigin[] =
51 "synced-notifications://welcome";
53 // SyncedNotificationAppInfo is a class that contains the information necessary
54 // to produce a welcome notification and the app badges for all synced
56 // TODO(dewittj): Convert this into a sync protobuf-backed data structure.
57 class SyncedNotificationAppInfo {
59 explicit SyncedNotificationAppInfo(const std::string& app_id,
60 const base::string16& service_name);
61 ~SyncedNotificationAppInfo();
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_; }
67 void set_icon(const gfx::Image& icon) { icon_ = icon; }
68 const gfx::Image& icon() const { return icon_; }
70 void set_small_icon(const gfx::Image& small_icon) {
71 small_icon_ = small_icon;
73 const gfx::Image& small_icon() const { return small_icon_; }
75 const message_center::NotifierId GetNotifierId() const;
79 base::string16 service_name_;
81 gfx::Image small_icon_;
82 base::string16 title_;
83 base::string16 message_;
86 SyncedNotificationAppInfo::SyncedNotificationAppInfo(
87 const std::string& app_id,
88 const base::string16& service_name)
89 : app_id_(app_id), service_name_(service_name) {
91 l10n_util::GetStringFUTF16(IDS_NOTIFIER_WELCOME_TITLE, service_name_);
94 SyncedNotificationAppInfo::~SyncedNotificationAppInfo() {}
96 const message_center::NotifierId SyncedNotificationAppInfo::GetNotifierId()
98 return message_center::NotifierId(
99 message_center::NotifierId::SYNCED_NOTIFICATION_SERVICE, app_id());
102 bool ChromeNotifierService::avoid_bitmap_fetching_for_test_ = false;
104 ChromeNotifierService::ChromeNotifierService(Profile* profile,
105 NotificationUIManager* manager)
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.
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);
124 ChromeNotifierService::~ChromeNotifierService() {}
126 // Methods from BrowserContextKeyedService.
127 void ChromeNotifierService::Shutdown() {}
129 // syncer::SyncableService implementation.
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();
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());
150 // Build a local notification object from the sync data.
151 scoped_ptr<SyncedNotification> incoming(CreateNotificationFromSyncData(
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";
160 // Process each incoming remote notification.
161 const std::string& key = incoming->GetKey();
162 DCHECK_GT(key.length(), 0U);
163 SyncedNotification* found = FindNotificationById(key);
166 // If there are no conflicts, copy in the data from remote.
167 Add(incoming.Pass());
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)) {
173 if (incoming->GetReadState() == found->GetReadState()) {
174 // Notification matches on the client and the server, nothing to do.
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());
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,
196 // If local state changed, notify Notification UI Manager.
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);
204 // Tell the notification manager to update the notification.
205 UpdateInMessageCenter(found);
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));
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);
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.
232 syncer::SyncDataList ChromeNotifierService::GetAllSyncData(
233 syncer::ModelType type) const {
234 DCHECK_EQ(syncer::SYNCED_NOTIFICATIONS, type);
235 syncer::SyncDataList sync_data;
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));
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;
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();
260 scoped_ptr<SyncedNotification> new_notification(
261 CreateNotificationFromSyncData(sync_data));
262 if (!new_notification.get()) {
263 NOTREACHED() << "Failed to read notification.";
267 const std::string& key = new_notification->GetKey();
268 DCHECK_GT(key.length(), 0U);
269 SyncedNotification* found = FindNotificationById(key);
271 switch (change_type) {
272 case syncer::SyncChange::ACTION_ADD:
273 // Intentional fall through, cases are identical.
274 case syncer::SyncChange::ACTION_UPDATE:
276 Add(new_notification.Pass());
279 // Update it in our store.
280 found->Update(sync_data);
281 // Tell the notification manager to update the notification.
282 UpdateInMessageCenter(found);
285 case syncer::SyncChange::ACTION_DELETE:
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?
306 // Support functions for data type conversion.
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());
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();
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>();
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);
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>();
366 // Create a new notification object based on the supplied sync_data.
367 scoped_ptr<SyncedNotification> notification(
368 new SyncedNotification(sync_data));
370 return notification.Pass();
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())
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);
403 void ChromeNotifierService::GetSyncedNotificationServices(
404 std::vector<message_center::Notifier*>* notifiers) {
405 // TODO(mukai|petewil): Check the profile's eligibility before adding the
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();
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(),
418 app_enabled = iter != enabled_sending_services_.end();
420 message_center::Notifier* app_info_notifier = new message_center::Notifier(
421 notifier_id, app_info->service_name(), app_enabled);
423 app_info_notifier->icon = app_info->small_icon();
425 // |notifiers| takes ownership of |app_info_notifier|.
426 notifiers->push_back(app_info_notifier);
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);
436 notification->NotificationHasBeenRead();
437 syncer::SyncChangeList new_changes;
439 syncer::SyncData sync_data = CreateSyncDataFromNotification(*notification);
440 new_changes.push_back(
441 syncer::SyncChange(FROM_HERE,
442 syncer::SyncChange::ACTION_UPDATE,
445 // Send up the changes that were made locally.
446 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes);
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());
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())
469 UpdateInMessageCenter(notification_copy);
472 void ChromeNotifierService::AddForTest(
473 scoped_ptr<notifier::SyncedNotification> notification) {
474 notification_data_.push_back(notification.release());
477 void ChromeNotifierService::UpdateInMessageCenter(
478 SyncedNotification* notification) {
479 // If the feature is disabled, exit now.
480 if (!notifier::ChromeNotifierServiceFactory::UseSyncedNotifications(
481 CommandLine::ForCurrentProcess()))
484 notification->LogNotification();
486 if (notification->GetReadState() == SyncedNotification::kUnread) {
487 // If the message is unread, update it.
488 Display(notification);
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());
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
500 if (synced_notification_first_run_) {
501 // Setting the toast state to false will prevent toasting the notification.
502 notification->SetToastState(false);
505 // Our tests cannot use the network for reliability reasons.
506 if (avoid_bitmap_fetching_for_test_) {
507 notification->Show(notification_manager_, this, profile_);
511 // Set up to fetch the bitmaps.
512 notification->QueueBitmapFetchJobs(notification_manager_, this, profile_);
514 // Start the bitmap fetching, Show() will be called when the last bitmap
515 // either arrives or times out.
516 notification->StartBitmapFetch();
519 void ChromeNotifierService::OnSyncedNotificationServiceEnabled(
520 const std::string& notifier_id, bool enabled) {
521 std::set<std::string>::iterator iter;
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);
527 iter = find(enabled_sending_services_.begin(),
528 enabled_sending_services_.end(),
531 base::ListValue synced_notification_services;
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);
554 // Collect UMA statistics when a service is enabled or disabled.
556 content::RecordAction(
557 UserMetricsAction("SyncedNotifications.SendingServiceEnabled"));
559 content::RecordAction(
560 UserMetricsAction("SyncedNotifications.SendingServiceDisabled"));
563 // Collect individual service enabling/disabling statistics.
564 CollectPerServiceEnablingStatistics(notifier_id, enabled);
569 void ChromeNotifierService::CollectPerServiceEnablingStatistics(
570 const std::string& notifier_id,
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
578 ChromeNotifierServiceActionType action =
579 CHROME_NOTIFIER_SERVICE_ACTION_UNKNOWN;
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)) {
585 ? CHROME_NOTIFIER_SERVICE_ACTION_FIRST_SERVICE_ENABLED
586 : CHROME_NOTIFIER_SERVICE_ACTION_FIRST_SERVICE_DISABLED;
589 UMA_HISTOGRAM_ENUMERATION("ChromeNotifierService.Actions",
591 CHROME_NOTIFIER_SERVICE_ACTION_COUNT);
594 void ChromeNotifierService::BuildServiceListValueInplace(
595 std::set<std::string> services, base::ListValue* list_value) {
596 std::set<std::string>::iterator iter;
598 // Iterate over the strings, adding each one to the list value
599 for (iter = services.begin();
600 iter != services.end();
602 base::StringValue* string_value(new base::StringValue(*iter));
603 list_value->Append(string_value);
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();
613 if ((*iter)->GetSendingServiceId() == notifier_id &&
614 (*iter)->GetReadState() == SyncedNotification::kUnread)
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();
625 if ((*iter)->GetSendingServiceId() == notifier_id &&
626 (*iter)->GetReadState() == SyncedNotification::kUnread) {
627 notification_manager_->CancelById((*iter)->GetKey());
632 void ChromeNotifierService::OnEnabledSendingServiceListPrefChanged(
633 std::set<std::string>* ids_field) {
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);
642 LOG(WARNING) << i << "-th element is not a string "
643 << prefs::kEnabledSyncedNotificationSendingServices;
647 void ChromeNotifierService::OnInitializedSendingServiceListPrefChanged(
648 std::set<std::string>* ids_field) {
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);
657 LOG(WARNING) << i << "-th element is not a string for "
658 << prefs::kInitializedSyncedNotificationSendingServices;
662 void ChromeNotifierService::OnSyncedNotificationFirstRunBooleanPrefChanged(
664 synced_notification_first_run_ = *new_value;
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);
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(),
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(),
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(),
704 &ChromeNotifierService::
705 OnSyncedNotificationFirstRunBooleanPrefChanged,
706 base::Unretained(this),
707 base::Unretained(&synced_notification_first_run_)));
709 // Get the prefs from last session into our memeber varilables
710 OnEnabledSendingServiceListPrefChanged(&enabled_sending_services_);
711 OnInitializedSendingServiceListPrefChanged(&initialized_sending_services_);
713 synced_notification_first_run_ =
714 profile_->GetPrefs()->GetBoolean(prefs::kSyncedNotificationFirstRun);
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();
723 std::set<std::string>::iterator iter;
724 iter = find(initialized_sending_services_.begin(),
725 initialized_sending_services_.end(),
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())
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);
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);
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;
754 message_center::NotifierId notifier_id(
755 message_center::NotifierId::SYNCED_NOTIFICATION_SERVICE,
758 Notification notification = CreateWelcomeNotificationForService(app_info);
759 notification_ui_manager->Add(notification, profile_);
761 enabled_sending_services_.insert(sending_service_id);
762 initialized_sending_services_.insert(sending_service_id);
764 // Build a ListValue with the list of services to be enabled.
765 base::ListValue enabled_sending_services;
766 base::ListValue initialized_sending_services;
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);
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)
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()));
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);
806 message_center::RichNotificationData rich_notification_data;
807 rich_notification_data.buttons.push_back(button_info);
809 message_center::NOTIFICATION_TYPE_BASE_FORMAT,
810 GURL(kSyncedNotificationsWelcomeOrigin),
812 l10n_util::GetStringUTF16(IDS_NOTIFIER_WELCOME_BODY),
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,
822 } // namespace notifier