desktop_notification_service.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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#include "chrome/browser/notifications/desktop_notification_service.h"
6
7#include "base/metrics/histogram.h"
8#include "base/prefs/scoped_user_pref_update.h"
9#include "base/strings/utf_string_conversions.h"
10#include "base/threading/thread.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chrome/browser/content_settings/content_settings_details.h"
14#include "chrome/browser/content_settings/content_settings_provider.h"
15#include "chrome/browser/content_settings/host_content_settings_map.h"
16#include "chrome/browser/extensions/api/notifications/notifications_api.h"
17#include "chrome/browser/extensions/extension_service.h"
18#include "chrome/browser/infobars/confirm_infobar_delegate.h"
19#include "chrome/browser/infobars/infobar.h"
20#include "chrome/browser/infobars/infobar_service.h"
21#include "chrome/browser/notifications/desktop_notification_service_factory.h"
22#include "chrome/browser/notifications/notification.h"
23#include "chrome/browser/notifications/notification_object_proxy.h"
24#include "chrome/browser/notifications/notification_ui_manager.h"
25#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
26#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
27#include "chrome/browser/profiles/profile.h"
28#include "chrome/browser/ui/browser.h"
29#include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
30#include "chrome/browser/ui/website_settings/permission_bubble_request.h"
31#include "chrome/common/content_settings.h"
32#include "chrome/common/content_settings_pattern.h"
33#include "chrome/common/pref_names.h"
34#include "chrome/common/url_constants.h"
35#include "components/user_prefs/pref_registry_syncable.h"
36#include "content/public/browser/browser_thread.h"
37#include "content/public/browser/notification_service.h"
38#include "content/public/browser/render_view_host.h"
39#include "content/public/browser/web_contents.h"
40#include "content/public/common/show_desktop_notification_params.h"
41#include "extensions/browser/event_router.h"
42#include "extensions/browser/extension_system.h"
43#include "extensions/browser/info_map.h"
44#include "extensions/common/constants.h"
45#include "extensions/common/extension.h"
46#include "extensions/common/extension_set.h"
47#include "grit/browser_resources.h"
48#include "grit/chromium_strings.h"
49#include "grit/generated_resources.h"
50#include "grit/theme_resources.h"
51#include "net/base/escape.h"
52#include "ui/base/l10n/l10n_util.h"
53#include "ui/base/resource/resource_bundle.h"
54#include "ui/base/webui/web_ui_util.h"
55#include "ui/message_center/message_center_util.h"
56#include "ui/message_center/notifier_settings.h"
57
58using content::BrowserThread;
59using content::RenderViewHost;
60using content::WebContents;
61using message_center::NotifierId;
62using blink::WebTextDirection;
63
64const char kChromeNowExtensionID[] = "pafkbggdmjlpgkdkcbjmhmfcdpncadgh";
65
66// NotificationPermissionRequest ---------------------------------------
67
68class NotificationPermissionRequest : public PermissionBubbleRequest {
69 public:
70  NotificationPermissionRequest(
71      DesktopNotificationService* notification_service,
72      const GURL& origin,
73      base::string16 display_name,
74      int process_id,
75      int route_id,
76      int callback_context);
77  virtual ~NotificationPermissionRequest();
78
79  // PermissionBubbleDelegate:
80  virtual base::string16 GetMessageText() const OVERRIDE;
81  virtual base::string16 GetMessageTextFragment() const OVERRIDE;
82  virtual base::string16 GetAlternateAcceptButtonText() const OVERRIDE;
83  virtual base::string16 GetAlternateDenyButtonText() const OVERRIDE;
84  virtual void PermissionGranted() OVERRIDE;
85  virtual void PermissionDenied() OVERRIDE;
86  virtual void Cancelled() OVERRIDE;
87  virtual void RequestFinished() OVERRIDE;
88
89 private:
90  // The notification service to be used.
91  DesktopNotificationService* notification_service_;
92
93  // The origin we are asking for permissions on.
94  GURL origin_;
95
96  // The display name for the origin to be displayed.  Will be different from
97  // origin_ for extensions.
98  base::string16 display_name_;
99
100  // The callback information that tells us how to respond to javascript via
101  // the correct RenderView.
102  int process_id_;
103  int route_id_;
104  int callback_context_;
105
106  // Whether the user clicked one of the buttons.
107  bool action_taken_;
108
109  DISALLOW_COPY_AND_ASSIGN(NotificationPermissionRequest);
110};
111
112NotificationPermissionRequest::NotificationPermissionRequest(
113    DesktopNotificationService* notification_service,
114    const GURL& origin,
115    base::string16 display_name,
116    int process_id,
117    int route_id,
118    int callback_context)
119    : notification_service_(notification_service),
120      origin_(origin),
121      display_name_(display_name),
122      process_id_(process_id),
123      route_id_(route_id),
124      callback_context_(callback_context),
125      action_taken_(false) {}
126
127NotificationPermissionRequest::~NotificationPermissionRequest() {}
128
129base::string16 NotificationPermissionRequest::GetMessageText() const {
130  return l10n_util::GetStringFUTF16(IDS_NOTIFICATION_PERMISSIONS,
131                                    display_name_);
132}
133
134base::string16
135NotificationPermissionRequest::GetMessageTextFragment() const {
136  return l10n_util::GetStringUTF16(IDS_NOTIFICATION_PERMISSIONS_FRAGMENT);
137}
138
139base::string16
140NotificationPermissionRequest::GetAlternateAcceptButtonText() const {
141  return l10n_util::GetStringUTF16(IDS_NOTIFICATION_PERMISSION_YES);
142}
143
144base::string16
145NotificationPermissionRequest::GetAlternateDenyButtonText() const {
146  return l10n_util::GetStringUTF16(IDS_NOTIFICATION_PERMISSION_NO);
147}
148
149void NotificationPermissionRequest::PermissionGranted() {
150  action_taken_ = true;
151  UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Allowed", 1);
152  notification_service_->GrantPermission(origin_);
153}
154
155void NotificationPermissionRequest::PermissionDenied() {
156  action_taken_ = true;
157  UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Denied", 1);
158  notification_service_->DenyPermission(origin_);
159}
160
161void NotificationPermissionRequest::Cancelled() {
162}
163
164void NotificationPermissionRequest::RequestFinished() {
165  if (!action_taken_)
166    UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Ignored", 1);
167
168  RenderViewHost* host = RenderViewHost::FromID(process_id_, route_id_);
169  if (host)
170    host->DesktopNotificationPermissionRequestDone(callback_context_);
171
172  delete this;
173}
174
175
176// NotificationPermissionInfoBarDelegate --------------------------------------
177
178// The delegate for the infobar shown when an origin requests notification
179// permissions.
180class NotificationPermissionInfoBarDelegate : public ConfirmInfoBarDelegate {
181 public:
182  // Creates a notification permission infobar and delegate and adds the infobar
183  // to |infobar_service|.
184  static void Create(InfoBarService* infobar_service,
185                     DesktopNotificationService* notification_service,
186                     const GURL& origin,
187                     const base::string16& display_name,
188                     int process_id,
189                     int route_id,
190                     int callback_context);
191
192 private:
193  NotificationPermissionInfoBarDelegate(
194      DesktopNotificationService* notification_service,
195      const GURL& origin,
196      const base::string16& display_name,
197      int process_id,
198      int route_id,
199      int callback_context);
200  virtual ~NotificationPermissionInfoBarDelegate();
201
202  // ConfirmInfoBarDelegate:
203  virtual int GetIconID() const OVERRIDE;
204  virtual Type GetInfoBarType() const OVERRIDE;
205  virtual base::string16 GetMessageText() const OVERRIDE;
206  virtual base::string16 GetButtonLabel(InfoBarButton button) const OVERRIDE;
207  virtual bool Accept() OVERRIDE;
208  virtual bool Cancel() OVERRIDE;
209
210  // The origin we are asking for permissions on.
211  GURL origin_;
212
213  // The display name for the origin to be displayed.  Will be different from
214  // origin_ for extensions.
215  base::string16 display_name_;
216
217  // The notification service to be used.
218  DesktopNotificationService* notification_service_;
219
220  // The callback information that tells us how to respond to javascript via
221  // the correct RenderView.
222  int process_id_;
223  int route_id_;
224  int callback_context_;
225
226  // Whether the user clicked one of the buttons.
227  bool action_taken_;
228
229  DISALLOW_COPY_AND_ASSIGN(NotificationPermissionInfoBarDelegate);
230};
231
232// static
233void NotificationPermissionInfoBarDelegate::Create(
234    InfoBarService* infobar_service,
235    DesktopNotificationService* notification_service,
236    const GURL& origin,
237    const base::string16& display_name,
238    int process_id,
239    int route_id,
240    int callback_context) {
241  infobar_service->AddInfoBar(ConfirmInfoBarDelegate::CreateInfoBar(
242      scoped_ptr<ConfirmInfoBarDelegate>(
243          new NotificationPermissionInfoBarDelegate(
244              notification_service, origin, display_name, process_id, route_id,
245              callback_context))));
246}
247
248NotificationPermissionInfoBarDelegate::NotificationPermissionInfoBarDelegate(
249    DesktopNotificationService* notification_service,
250    const GURL& origin,
251    const base::string16& display_name,
252    int process_id,
253    int route_id,
254    int callback_context)
255    : ConfirmInfoBarDelegate(),
256      origin_(origin),
257      display_name_(display_name),
258      notification_service_(notification_service),
259      process_id_(process_id),
260      route_id_(route_id),
261      callback_context_(callback_context),
262      action_taken_(false) {
263}
264
265NotificationPermissionInfoBarDelegate::
266    ~NotificationPermissionInfoBarDelegate() {
267  if (!action_taken_)
268    UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Ignored", 1);
269
270  RenderViewHost* host = RenderViewHost::FromID(process_id_, route_id_);
271  if (host)
272    host->DesktopNotificationPermissionRequestDone(callback_context_);
273}
274
275int NotificationPermissionInfoBarDelegate::GetIconID() const {
276  return IDR_INFOBAR_DESKTOP_NOTIFICATIONS;
277}
278
279InfoBarDelegate::Type
280    NotificationPermissionInfoBarDelegate::GetInfoBarType() const {
281  return PAGE_ACTION_TYPE;
282}
283
284base::string16 NotificationPermissionInfoBarDelegate::GetMessageText() const {
285  return l10n_util::GetStringFUTF16(IDS_NOTIFICATION_PERMISSIONS,
286                                    display_name_);
287}
288
289base::string16 NotificationPermissionInfoBarDelegate::GetButtonLabel(
290    InfoBarButton button) const {
291  return l10n_util::GetStringUTF16((button == BUTTON_OK) ?
292      IDS_NOTIFICATION_PERMISSION_YES : IDS_NOTIFICATION_PERMISSION_NO);
293}
294
295bool NotificationPermissionInfoBarDelegate::Accept() {
296  UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Allowed", 1);
297  notification_service_->GrantPermission(origin_);
298  action_taken_ = true;
299  return true;
300}
301
302bool NotificationPermissionInfoBarDelegate::Cancel() {
303  UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Denied", 1);
304  notification_service_->DenyPermission(origin_);
305  action_taken_ = true;
306  return true;
307}
308
309
310// DesktopNotificationService -------------------------------------------------
311
312// static
313void DesktopNotificationService::RegisterProfilePrefs(
314    user_prefs::PrefRegistrySyncable* registry) {
315  registry->RegisterListPref(
316      prefs::kMessageCenterDisabledExtensionIds,
317      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
318  registry->RegisterListPref(
319      prefs::kMessageCenterDisabledSystemComponentIds,
320      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
321  registry->RegisterListPref(
322      prefs::kMessageCenterEnabledSyncNotifierIds,
323      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
324  ExtensionWelcomeNotification::RegisterProfilePrefs(registry);
325}
326
327// static
328base::string16 DesktopNotificationService::CreateDataUrl(
329    const GURL& icon_url,
330    const base::string16& title,
331    const base::string16& body,
332    WebTextDirection dir) {
333  int resource;
334  std::vector<std::string> subst;
335  if (icon_url.is_valid()) {
336    resource = IDR_NOTIFICATION_ICON_HTML;
337    subst.push_back(icon_url.spec());
338    subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title)));
339    subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body)));
340    // icon float position
341    subst.push_back(dir == blink::WebTextDirectionRightToLeft ?
342                    "right" : "left");
343  } else if (title.empty() || body.empty()) {
344    resource = IDR_NOTIFICATION_1LINE_HTML;
345    base::string16 line = title.empty() ? body : title;
346    // Strings are div names in the template file.
347    base::string16 line_name =
348        title.empty() ? base::ASCIIToUTF16("description")
349                      : base::ASCIIToUTF16("title");
350    subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line_name)));
351    subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line)));
352  } else {
353    resource = IDR_NOTIFICATION_2LINE_HTML;
354    subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title)));
355    subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body)));
356  }
357  // body text direction
358  subst.push_back(dir == blink::WebTextDirectionRightToLeft ?
359                  "rtl" : "ltr");
360
361  return CreateDataUrl(resource, subst);
362}
363
364// static
365base::string16 DesktopNotificationService::CreateDataUrl(
366    int resource, const std::vector<std::string>& subst) {
367  const base::StringPiece template_html(
368      ResourceBundle::GetSharedInstance().GetRawDataResource(
369          resource));
370
371  if (template_html.empty()) {
372    NOTREACHED() << "unable to load template. ID: " << resource;
373    return base::string16();
374  }
375
376  std::string data = ReplaceStringPlaceholders(template_html, subst, NULL);
377  return base::UTF8ToUTF16("data:text/html;charset=utf-8," +
378                               net::EscapeQueryParamValue(data, false));
379}
380
381// static
382std::string DesktopNotificationService::AddNotification(
383    const GURL& origin_url,
384    const base::string16& title,
385    const base::string16& message,
386    const GURL& icon_url,
387    const base::string16& replace_id,
388    NotificationDelegate* delegate,
389    Profile* profile) {
390  if (message_center::IsRichNotificationEnabled()) {
391    // For message center create a non-HTML notification with |icon_url|.
392    Notification notification(origin_url, icon_url, title, message,
393                              blink::WebTextDirectionDefault,
394                              base::string16(), replace_id, delegate);
395    g_browser_process->notification_ui_manager()->Add(notification, profile);
396    return notification.notification_id();
397  }
398
399  // Generate a data URL embedding the icon URL, title, and message.
400  GURL content_url(CreateDataUrl(
401      icon_url, title, message, blink::WebTextDirectionDefault));
402  Notification notification(
403      GURL(), content_url, base::string16(), replace_id, delegate);
404  g_browser_process->notification_ui_manager()->Add(notification, profile);
405  return notification.notification_id();
406}
407
408// static
409std::string DesktopNotificationService::AddIconNotification(
410    const GURL& origin_url,
411    const base::string16& title,
412    const base::string16& message,
413    const gfx::Image& icon,
414    const base::string16& replace_id,
415    NotificationDelegate* delegate,
416    Profile* profile) {
417  if (message_center::IsRichNotificationEnabled()) {
418    // For message center create a non-HTML notification with |icon|.
419    Notification notification(origin_url, icon, title, message,
420                              blink::WebTextDirectionDefault,
421                              base::string16(), replace_id, delegate);
422    g_browser_process->notification_ui_manager()->Add(notification, profile);
423    return notification.notification_id();
424  }
425
426  GURL icon_url;
427  if (!icon.IsEmpty())
428    icon_url = GURL(webui::GetBitmapDataUrl(*icon.ToSkBitmap()));
429  return AddNotification(
430      origin_url, title, message, icon_url, replace_id, delegate, profile);
431}
432
433// static
434void DesktopNotificationService::RemoveNotification(
435    const std::string& notification_id) {
436    g_browser_process->notification_ui_manager()->CancelById(notification_id);
437}
438
439DesktopNotificationService::DesktopNotificationService(
440    Profile* profile,
441    NotificationUIManager* ui_manager)
442    : profile_(profile),
443      ui_manager_(ui_manager) {
444  OnStringListPrefChanged(
445      prefs::kMessageCenterDisabledExtensionIds, &disabled_extension_ids_);
446  OnStringListPrefChanged(
447      prefs::kMessageCenterDisabledSystemComponentIds,
448      &disabled_system_component_ids_);
449  OnStringListPrefChanged(
450      prefs::kMessageCenterEnabledSyncNotifierIds, &enabled_sync_notifier_ids_);
451  disabled_extension_id_pref_.Init(
452      prefs::kMessageCenterDisabledExtensionIds,
453      profile_->GetPrefs(),
454      base::Bind(
455          &DesktopNotificationService::OnStringListPrefChanged,
456          base::Unretained(this),
457          base::Unretained(prefs::kMessageCenterDisabledExtensionIds),
458          base::Unretained(&disabled_extension_ids_)));
459  disabled_system_component_id_pref_.Init(
460      prefs::kMessageCenterDisabledSystemComponentIds,
461      profile_->GetPrefs(),
462      base::Bind(
463          &DesktopNotificationService::OnStringListPrefChanged,
464          base::Unretained(this),
465          base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds),
466          base::Unretained(&disabled_system_component_ids_)));
467  enabled_sync_notifier_id_pref_.Init(
468      prefs::kMessageCenterEnabledSyncNotifierIds,
469      profile_->GetPrefs(),
470      base::Bind(
471          &DesktopNotificationService::OnStringListPrefChanged,
472          base::Unretained(this),
473          base::Unretained(prefs::kMessageCenterEnabledSyncNotifierIds),
474          base::Unretained(&enabled_sync_notifier_ids_)));
475  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
476                 content::Source<Profile>(profile_));
477}
478
479DesktopNotificationService::~DesktopNotificationService() {
480}
481
482void DesktopNotificationService::GrantPermission(const GURL& origin) {
483  ContentSettingsPattern primary_pattern =
484      ContentSettingsPattern::FromURLNoWildcard(origin);
485  profile_->GetHostContentSettingsMap()->SetContentSetting(
486      primary_pattern,
487      ContentSettingsPattern::Wildcard(),
488      CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
489      NO_RESOURCE_IDENTIFIER,
490      CONTENT_SETTING_ALLOW);
491}
492
493void DesktopNotificationService::DenyPermission(const GURL& origin) {
494  ContentSettingsPattern primary_pattern =
495      ContentSettingsPattern::FromURLNoWildcard(origin);
496  profile_->GetHostContentSettingsMap()->SetContentSetting(
497      primary_pattern,
498      ContentSettingsPattern::Wildcard(),
499      CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
500      NO_RESOURCE_IDENTIFIER,
501      CONTENT_SETTING_BLOCK);
502}
503
504ContentSetting DesktopNotificationService::GetDefaultContentSetting(
505    std::string* provider_id) {
506  return profile_->GetHostContentSettingsMap()->GetDefaultContentSetting(
507      CONTENT_SETTINGS_TYPE_NOTIFICATIONS, provider_id);
508}
509
510void DesktopNotificationService::SetDefaultContentSetting(
511    ContentSetting setting) {
512  profile_->GetHostContentSettingsMap()->SetDefaultContentSetting(
513      CONTENT_SETTINGS_TYPE_NOTIFICATIONS, setting);
514}
515
516void DesktopNotificationService::ResetToDefaultContentSetting() {
517  profile_->GetHostContentSettingsMap()->SetDefaultContentSetting(
518      CONTENT_SETTINGS_TYPE_NOTIFICATIONS, CONTENT_SETTING_DEFAULT);
519}
520
521void DesktopNotificationService::GetNotificationsSettings(
522    ContentSettingsForOneType* settings) {
523  profile_->GetHostContentSettingsMap()->GetSettingsForOneType(
524      CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
525      NO_RESOURCE_IDENTIFIER,
526      settings);
527}
528
529void DesktopNotificationService::ClearSetting(
530    const ContentSettingsPattern& pattern) {
531  profile_->GetHostContentSettingsMap()->SetContentSetting(
532      pattern,
533      ContentSettingsPattern::Wildcard(),
534      CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
535      NO_RESOURCE_IDENTIFIER,
536      CONTENT_SETTING_DEFAULT);
537}
538
539void DesktopNotificationService::ResetAllOrigins() {
540  profile_->GetHostContentSettingsMap()->ClearSettingsForOneType(
541      CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
542}
543
544ContentSetting DesktopNotificationService::GetContentSetting(
545    const GURL& origin) {
546  return profile_->GetHostContentSettingsMap()->GetContentSetting(
547      origin,
548      origin,
549      CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
550      NO_RESOURCE_IDENTIFIER);
551}
552
553void DesktopNotificationService::RequestPermission(
554    const GURL& origin, int process_id, int route_id, int callback_context,
555    WebContents* contents) {
556  // If |origin| hasn't been seen before and the default content setting for
557  // notifications is "ask", show an infobar.
558  // The cache can only answer queries on the IO thread once it's initialized,
559  // so don't ask the cache.
560  ContentSetting setting = GetContentSetting(origin);
561  if (setting == CONTENT_SETTING_ASK) {
562    if (PermissionBubbleManager::Enabled()) {
563      PermissionBubbleManager* bubble_manager =
564          PermissionBubbleManager::FromWebContents(contents);
565      bubble_manager->AddRequest(new NotificationPermissionRequest(this,
566              origin, DisplayNameForOriginInProcessId(origin, process_id),
567              process_id, route_id, callback_context));
568      return;
569    }
570
571    // Show an info bar requesting permission.
572    InfoBarService* infobar_service =
573        InfoBarService::FromWebContents(contents);
574    // |infobar_service| may be NULL, e.g., if this request originated in a
575    // browser action popup, extension background page, or any HTML that runs
576    // outside of a tab.
577    if (infobar_service) {
578      NotificationPermissionInfoBarDelegate::Create(
579          infobar_service, this,
580          origin, DisplayNameForOriginInProcessId(origin, process_id),
581          process_id, route_id, callback_context);
582      return;
583    }
584  }
585
586  // Notify renderer immediately.
587  RenderViewHost* host = RenderViewHost::FromID(process_id, route_id);
588  if (host)
589    host->DesktopNotificationPermissionRequestDone(callback_context);
590}
591
592#if !defined(OS_WIN)
593void DesktopNotificationService::ShowNotification(
594    const Notification& notification) {
595  GetUIManager()->Add(notification, profile_);
596}
597
598bool DesktopNotificationService::CancelDesktopNotification(
599    int process_id, int route_id, int notification_id) {
600  scoped_refptr<NotificationObjectProxy> proxy(
601      new NotificationObjectProxy(process_id, route_id, notification_id,
602                                  false));
603  return GetUIManager()->CancelById(proxy->id());
604}
605#endif  // OS_WIN
606
607bool DesktopNotificationService::ShowDesktopNotification(
608    const content::ShowDesktopNotificationHostMsgParams& params,
609    int process_id, int route_id, DesktopNotificationSource source) {
610  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
611  const GURL& origin = params.origin;
612  NotificationObjectProxy* proxy =
613      new NotificationObjectProxy(process_id, route_id,
614                                  params.notification_id,
615                                  source == WorkerNotification);
616
617  base::string16 display_source =
618      DisplayNameForOriginInProcessId(origin, process_id);
619  Notification notification(origin, params.icon_url, params.title,
620      params.body, params.direction, display_source, params.replace_id,
621      proxy);
622
623  // The webkit notification doesn't timeout.
624  notification.set_never_timeout(true);
625
626  ShowNotification(notification);
627  return true;
628}
629
630base::string16 DesktopNotificationService::DisplayNameForOriginInProcessId(
631    const GURL& origin, int process_id) {
632  // If the source is an extension, lookup the display name.
633  // Message center prefers to use extension name if the notification
634  // is allowed by an extension.
635  if (NotificationUIManager::DelegatesToMessageCenter() ||
636      origin.SchemeIs(extensions::kExtensionScheme)) {
637    extensions::InfoMap* extension_info_map =
638        extensions::ExtensionSystem::Get(profile_)->info_map();
639    if (extension_info_map) {
640      extensions::ExtensionSet extensions;
641      extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
642          origin, process_id, extensions::APIPermission::kNotification,
643          &extensions);
644      for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
645           iter != extensions.end(); ++iter) {
646        NotifierId notifier_id(NotifierId::APPLICATION, (*iter)->id());
647        if (IsNotifierEnabled(notifier_id))
648          return base::UTF8ToUTF16((*iter)->name());
649      }
650    }
651  }
652  return base::UTF8ToUTF16(origin.host());
653}
654
655void DesktopNotificationService::NotifySettingsChange() {
656  content::NotificationService::current()->Notify(
657      chrome::NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED,
658      content::Source<DesktopNotificationService>(this),
659      content::NotificationService::NoDetails());
660}
661
662NotificationUIManager* DesktopNotificationService::GetUIManager() {
663  // We defer setting ui_manager_ to the global singleton until we need it
664  // in order to avoid UI dependent construction during startup.
665  if (!ui_manager_)
666    ui_manager_ = g_browser_process->notification_ui_manager();
667  return ui_manager_;
668}
669
670bool DesktopNotificationService::IsNotifierEnabled(
671    const NotifierId& notifier_id) {
672  switch (notifier_id.type) {
673    case NotifierId::APPLICATION:
674      return disabled_extension_ids_.find(notifier_id.id) ==
675          disabled_extension_ids_.end();
676    case NotifierId::WEB_PAGE:
677      return GetContentSetting(notifier_id.url) == CONTENT_SETTING_ALLOW;
678    case NotifierId::SYSTEM_COMPONENT:
679#if defined(OS_CHROMEOS)
680      return disabled_system_component_ids_.find(notifier_id.id) ==
681          disabled_system_component_ids_.end();
682#else
683      // We do not disable system component notifications.
684      return true;
685#endif
686    case NotifierId::SYNCED_NOTIFICATION_SERVICE:
687      return enabled_sync_notifier_ids_.find(notifier_id.id) !=
688          enabled_sync_notifier_ids_.end();
689  }
690
691  NOTREACHED();
692  return false;
693}
694
695void DesktopNotificationService::SetNotifierEnabled(
696    const NotifierId& notifier_id,
697    bool enabled) {
698  DCHECK_NE(NotifierId::WEB_PAGE, notifier_id.type);
699
700  bool add_new_item = false;
701  const char* pref_name = NULL;
702  scoped_ptr<base::StringValue> id;
703  switch (notifier_id.type) {
704    case NotifierId::APPLICATION:
705      pref_name = prefs::kMessageCenterDisabledExtensionIds;
706      add_new_item = !enabled;
707      id.reset(new base::StringValue(notifier_id.id));
708      FirePermissionLevelChangedEvent(notifier_id, enabled);
709      break;
710    case NotifierId::SYSTEM_COMPONENT:
711#if defined(OS_CHROMEOS)
712      pref_name = prefs::kMessageCenterDisabledSystemComponentIds;
713      add_new_item = !enabled;
714      id.reset(new base::StringValue(notifier_id.id));
715#else
716      return;
717#endif
718      break;
719    case NotifierId::SYNCED_NOTIFICATION_SERVICE:
720      pref_name = prefs::kMessageCenterEnabledSyncNotifierIds;
721      // Adding a new item if |enabled| == true, since synced notification
722      // services are opt-in.
723      add_new_item = enabled;
724      id.reset(new base::StringValue(notifier_id.id));
725      break;
726    default:
727      NOTREACHED();
728  }
729  DCHECK(pref_name != NULL);
730
731  ListPrefUpdate update(profile_->GetPrefs(), pref_name);
732  base::ListValue* const list = update.Get();
733  if (add_new_item) {
734    // AppendIfNotPresent will delete |adding_value| when the same value
735    // already exists.
736    list->AppendIfNotPresent(id.release());
737  } else {
738    list->Remove(*id, NULL);
739  }
740}
741
742void DesktopNotificationService::ShowWelcomeNotificationIfNecessary(
743    const Notification& notification) {
744  if (!chrome_now_welcome_notification_ &&
745      message_center::IsRichNotificationEnabled()) {
746    chrome_now_welcome_notification_ =
747        ExtensionWelcomeNotification::Create(kChromeNowExtensionID, profile_);
748  }
749
750  if (chrome_now_welcome_notification_) {
751    chrome_now_welcome_notification_->ShowWelcomeNotificationIfNecessary(
752        notification);
753  }
754}
755
756void DesktopNotificationService::OnStringListPrefChanged(
757    const char* pref_name, std::set<std::string>* ids_field) {
758  ids_field->clear();
759  // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
760  const PrefService* pref_service = profile_->GetPrefs();
761  CHECK(pref_service);
762  const base::ListValue* pref_list = pref_service->GetList(pref_name);
763  for (size_t i = 0; i < pref_list->GetSize(); ++i) {
764    std::string element;
765    if (pref_list->GetString(i, &element) && !element.empty())
766      ids_field->insert(element);
767    else
768      LOG(WARNING) << i << "-th element is not a string for " << pref_name;
769  }
770}
771
772void DesktopNotificationService::Observe(
773    int type,
774    const content::NotificationSource& source,
775    const content::NotificationDetails& details) {
776  DCHECK_EQ(chrome::NOTIFICATION_EXTENSION_UNINSTALLED, type);
777
778  extensions::Extension* extension =
779      content::Details<extensions::Extension>(details).ptr();
780  NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
781  if (IsNotifierEnabled(notifier_id))
782    return;
783
784  // The settings for ephemeral apps will be persisted across cache evictions.
785  if (extension->is_ephemeral())
786    return;
787
788  SetNotifierEnabled(notifier_id, true);
789}
790
791void DesktopNotificationService::FirePermissionLevelChangedEvent(
792    const NotifierId& notifier_id, bool enabled) {
793  DCHECK_EQ(NotifierId::APPLICATION, notifier_id.type);
794  extensions::api::notifications::PermissionLevel permission =
795      enabled ? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
796              : extensions::api::notifications::PERMISSION_LEVEL_DENIED;
797  scoped_ptr<base::ListValue> args(new base::ListValue());
798  args->Append(new base::StringValue(
799      extensions::api::notifications::ToString(permission)));
800  scoped_ptr<extensions::Event> event(new extensions::Event(
801      extensions::api::notifications::OnPermissionLevelChanged::kEventName,
802      args.Pass()));
803  extensions::ExtensionSystem::Get(profile_)->event_router()->
804      DispatchEventToExtension(notifier_id.id, event.Pass());
805
806  // Tell the IO thread that this extension's permission for notifications
807  // has changed.
808  extensions::InfoMap* extension_info_map =
809      extensions::ExtensionSystem::Get(profile_)->info_map();
810  BrowserThread::PostTask(
811      BrowserThread::IO, FROM_HERE,
812      base::Bind(&extensions::InfoMap::SetNotificationsDisabled,
813                 extension_info_map, notifier_id.id, !enabled));
814
815}
816