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