1 // Copyright 2014 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 #include "chrome/browser/notifications/platform_notification_service_impl.h"
7 #include "base/command_line.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/browser_process.h"
12 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
13 #include "chrome/browser/notifications/desktop_notification_profile_util.h"
14 #include "chrome/browser/notifications/notification_object_proxy.h"
15 #include "chrome/browser/notifications/notification_ui_manager.h"
16 #include "chrome/browser/notifications/persistent_notification_delegate.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_io_data.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/chrome_pages.h"
21 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "components/content_settings/core/browser/host_content_settings_map.h"
26 #include "components/content_settings/core/common/content_settings.h"
27 #include "components/content_settings/core/common/content_settings_types.h"
28 #include "components/url_formatter/url_formatter.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/desktop_notification_delegate.h"
31 #include "content/public/browser/notification_event_dispatcher.h"
32 #include "content/public/browser/platform_notification_context.h"
33 #include "content/public/browser/storage_partition.h"
34 #include "content/public/common/platform_notification_data.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "ui/message_center/notifier_settings.h"
38 #include "ui/resources/grit/ui_resources.h"
39 #include "url/url_constants.h"
41 #if defined(ENABLE_EXTENSIONS)
42 #include "chrome/browser/notifications/notifier_state_tracker.h"
43 #include "chrome/browser/notifications/notifier_state_tracker_factory.h"
44 #include "extensions/browser/extension_registry.h"
45 #include "extensions/browser/info_map.h"
46 #include "extensions/common/constants.h"
47 #include "extensions/common/permissions/api_permission.h"
48 #include "extensions/common/permissions/permissions_data.h"
51 #if defined(OS_ANDROID)
52 #include "base/strings/string_number_conversions.h"
55 using content::BrowserContext
;
56 using content::BrowserThread
;
57 using content::PlatformNotificationContext
;
58 using message_center::NotifierId
;
62 // Callback to provide when deleting the data associated with persistent Web
63 // Notifications from the notification database.
64 void OnPersistentNotificationDataDeleted(bool success
) {
65 UMA_HISTOGRAM_BOOLEAN("Notifications.PersistentNotificationDataDeleted",
69 // Persistent notifications fired through the delegate do not care about the
70 // lifetime of the Service Worker responsible for executing the event.
71 void OnEventDispatchComplete(content::PersistentNotificationStatus status
) {
72 UMA_HISTOGRAM_ENUMERATION(
73 "Notifications.PersistentWebNotificationClickResult", status
,
74 content::PersistentNotificationStatus::
75 PERSISTENT_NOTIFICATION_STATUS_MAX
);
78 void CancelNotification(const std::string
& id
, ProfileID profile_id
) {
79 PlatformNotificationServiceImpl::GetInstance()
80 ->GetNotificationUIManager()->CancelById(id
, profile_id
);
86 PlatformNotificationServiceImpl
*
87 PlatformNotificationServiceImpl::GetInstance() {
88 return base::Singleton
<PlatformNotificationServiceImpl
>::get();
91 PlatformNotificationServiceImpl::PlatformNotificationServiceImpl()
92 : notification_ui_manager_for_tests_(nullptr) {}
94 PlatformNotificationServiceImpl::~PlatformNotificationServiceImpl() {}
96 void PlatformNotificationServiceImpl::OnPersistentNotificationClick(
97 BrowserContext
* browser_context
,
98 int64_t persistent_notification_id
,
100 int action_index
) const {
101 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
102 content::NotificationEventDispatcher::GetInstance()
103 ->DispatchNotificationClickEvent(
105 persistent_notification_id
,
108 base::Bind(&OnEventDispatchComplete
));
111 void PlatformNotificationServiceImpl::OnPersistentNotificationClose(
112 BrowserContext
* browser_context
,
113 int64_t persistent_notification_id
,
114 const GURL
& origin
) const {
115 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
116 PlatformNotificationContext
* context
=
117 BrowserContext::GetStoragePartitionForSite(browser_context
, origin
)
118 ->GetPlatformNotificationContext();
120 BrowserThread::PostTask(
123 base::Bind(&PlatformNotificationContext::DeleteNotificationData
,
125 persistent_notification_id
,
127 base::Bind(&OnPersistentNotificationDataDeleted
)));
130 blink::WebNotificationPermission
131 PlatformNotificationServiceImpl::CheckPermissionOnUIThread(
132 BrowserContext
* browser_context
,
134 int render_process_id
) {
135 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
137 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
140 #if defined(ENABLE_EXTENSIONS)
141 // Extensions support an API permission named "notification". This will grant
142 // not only grant permission for using the Chrome App extension API, but also
143 // for the Web Notification API.
144 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
145 extensions::ExtensionRegistry
* registry
=
146 extensions::ExtensionRegistry::Get(browser_context
);
147 extensions::ProcessMap
* process_map
=
148 extensions::ProcessMap::Get(browser_context
);
150 const extensions::Extension
* extension
=
151 registry
->GetExtensionById(origin
.host(),
152 extensions::ExtensionRegistry::ENABLED
);
155 extension
->permissions_data()->HasAPIPermission(
156 extensions::APIPermission::kNotifications
) &&
157 process_map
->Contains(extension
->id(), render_process_id
)) {
158 NotifierStateTracker
* notifier_state_tracker
=
159 NotifierStateTrackerFactory::GetForProfile(profile
);
160 DCHECK(notifier_state_tracker
);
162 NotifierId
notifier_id(NotifierId::APPLICATION
, extension
->id());
163 if (notifier_state_tracker
->IsNotifierEnabled(notifier_id
))
164 return blink::WebNotificationPermissionAllowed
;
169 ContentSetting setting
=
170 DesktopNotificationProfileUtil::GetContentSetting(profile
, origin
);
172 if (setting
== CONTENT_SETTING_ALLOW
)
173 return blink::WebNotificationPermissionAllowed
;
174 if (setting
== CONTENT_SETTING_BLOCK
)
175 return blink::WebNotificationPermissionDenied
;
177 return blink::WebNotificationPermissionDefault
;
180 blink::WebNotificationPermission
181 PlatformNotificationServiceImpl::CheckPermissionOnIOThread(
182 content::ResourceContext
* resource_context
,
184 int render_process_id
) {
185 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
187 ProfileIOData
* io_data
= ProfileIOData::FromResourceContext(resource_context
);
188 #if defined(ENABLE_EXTENSIONS)
189 // Extensions support an API permission named "notification". This will grant
190 // not only grant permission for using the Chrome App extension API, but also
191 // for the Web Notification API.
192 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
193 extensions::InfoMap
* extension_info_map
= io_data
->GetExtensionInfoMap();
194 const extensions::ProcessMap
& process_map
=
195 extension_info_map
->process_map();
197 const extensions::Extension
* extension
=
198 extension_info_map
->extensions().GetByID(origin
.host());
201 extension
->permissions_data()->HasAPIPermission(
202 extensions::APIPermission::kNotifications
) &&
203 process_map
.Contains(extension
->id(), render_process_id
)) {
204 if (!extension_info_map
->AreNotificationsDisabled(extension
->id()))
205 return blink::WebNotificationPermissionAllowed
;
210 // No enabled extensions exist, so check the normal host content settings.
211 HostContentSettingsMap
* host_content_settings_map
=
212 io_data
->GetHostContentSettingsMap();
213 ContentSetting setting
= host_content_settings_map
->GetContentSetting(
216 CONTENT_SETTINGS_TYPE_NOTIFICATIONS
,
217 content_settings::ResourceIdentifier());
219 if (setting
== CONTENT_SETTING_ALLOW
)
220 return blink::WebNotificationPermissionAllowed
;
221 if (setting
== CONTENT_SETTING_BLOCK
)
222 return blink::WebNotificationPermissionDenied
;
224 return blink::WebNotificationPermissionDefault
;
227 void PlatformNotificationServiceImpl::DisplayNotification(
228 BrowserContext
* browser_context
,
230 const SkBitmap
& icon
,
231 const content::PlatformNotificationData
& notification_data
,
232 scoped_ptr
<content::DesktopNotificationDelegate
> delegate
,
233 base::Closure
* cancel_callback
) {
234 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
236 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
238 DCHECK_EQ(0u, notification_data
.actions
.size());
240 NotificationObjectProxy
* proxy
=
241 new NotificationObjectProxy(browser_context
, delegate
.Pass());
242 Notification notification
= CreateNotificationFromData(
243 profile
, origin
, icon
, notification_data
, proxy
);
245 GetNotificationUIManager()->Add(notification
, profile
);
248 base::Bind(&CancelNotification
,
249 notification
.delegate_id(),
250 NotificationUIManager::GetProfileID(profile
));
252 HostContentSettingsMapFactory::GetForProfile(profile
)->UpdateLastUsage(
253 origin
, origin
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
);
256 void PlatformNotificationServiceImpl::DisplayPersistentNotification(
257 BrowserContext
* browser_context
,
258 int64_t persistent_notification_id
,
260 const SkBitmap
& icon
,
261 const content::PlatformNotificationData
& notification_data
) {
262 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
264 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
267 // The notification settings button will be appended after the developer-
268 // supplied buttons, available in |notification_data.actions|.
269 int settings_button_index
= notification_data
.actions
.size();
270 PersistentNotificationDelegate
* delegate
= new PersistentNotificationDelegate(
271 browser_context
, persistent_notification_id
, origin
,
272 settings_button_index
);
274 Notification notification
= CreateNotificationFromData(
275 profile
, origin
, icon
, notification_data
, delegate
);
277 // TODO(peter): Remove this mapping when we have reliable id generation for
278 // the message_center::Notification objects.
279 persistent_notifications_
[persistent_notification_id
] = notification
.id();
281 GetNotificationUIManager()->Add(notification
, profile
);
283 HostContentSettingsMapFactory::GetForProfile(profile
)->UpdateLastUsage(
284 origin
, origin
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
);
287 void PlatformNotificationServiceImpl::ClosePersistentNotification(
288 BrowserContext
* browser_context
,
289 int64_t persistent_notification_id
) {
290 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
292 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
295 #if defined(OS_ANDROID)
296 // TODO(peter): Remove this conversion when the notification ids are being
297 // generated by the caller of this method.
298 std::string textual_persistent_notification_id
=
299 base::Int64ToString(persistent_notification_id
);
300 GetNotificationUIManager()->CancelById(
301 textual_persistent_notification_id
,
302 NotificationUIManager::GetProfileID(profile
));
304 auto iter
= persistent_notifications_
.find(persistent_notification_id
);
305 if (iter
== persistent_notifications_
.end())
308 GetNotificationUIManager()->CancelById(
309 iter
->second
, NotificationUIManager::GetProfileID(profile
));
311 persistent_notifications_
.erase(iter
);
315 bool PlatformNotificationServiceImpl::GetDisplayedPersistentNotifications(
316 BrowserContext
* browser_context
,
317 std::set
<std::string
>* displayed_notifications
) {
318 DCHECK(displayed_notifications
);
320 #if !defined(OS_ANDROID)
321 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
322 if (!profile
|| profile
->AsTestingProfile())
323 return false; // Tests will not have a message center.
325 // TODO(peter): Filter for persistent notifications only.
326 *displayed_notifications
= GetNotificationUIManager()->GetAllIdsByProfile(
327 NotificationUIManager::GetProfileID(profile
));
331 // Android cannot reliably return the notifications that are currently being
332 // displayed on the platform, see the comment in NotificationUIManagerAndroid.
334 #endif // !defined(OS_ANDROID)
337 Notification
PlatformNotificationServiceImpl::CreateNotificationFromData(
340 const SkBitmap
& icon
,
341 const content::PlatformNotificationData
& notification_data
,
342 NotificationDelegate
* delegate
) const {
343 // TODO(peter): Icons for Web Notifications are currently always requested for
344 // 1x scale, whereas the displays on which they can be displayed can have a
345 // different pixel density. Be smarter about this when the API gets updated
346 // with a way for developers to specify images of different resolutions.
347 Notification
notification(
348 origin
, notification_data
.title
, notification_data
.body
,
349 gfx::Image::CreateFrom1xBitmap(icon
), base::UTF8ToUTF16(origin
.host()),
350 notification_data
.tag
, delegate
);
352 notification
.set_context_message(
353 DisplayNameForContextMessage(profile
, origin
));
354 notification
.set_vibration_pattern(notification_data
.vibration_pattern
);
355 notification
.set_silent(notification_data
.silent
);
357 std::vector
<message_center::ButtonInfo
> buttons
;
359 // Developer supplied buttons.
360 for (const auto& action
: notification_data
.actions
)
361 buttons
.push_back(message_center::ButtonInfo(action
.title
));
363 // Android always includes the settings button in all notifications, whereas for
364 // desktop only web (not extensions) notifications do.
365 #if !defined(OS_ANDROID)
366 // The notification Settings button always comes at the end.
367 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
368 switches::kNotificationSettingsButton
)) {
369 message_center::ButtonInfo settings_button
= message_center::ButtonInfo(
370 l10n_util::GetStringUTF16(IDS_NOTIFICATION_SETTINGS
));
371 settings_button
.icon
=
372 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
373 IDR_NOTIFICATION_SETTINGS
);
374 buttons
.push_back(settings_button
);
376 #endif // !defined(OS_ANDROID)
378 notification
.set_buttons(buttons
);
380 // On desktop, notifications with require_interaction==true stay on-screen
381 // rather than minimizing to the notification center after a timeout.
382 // On mobile, this is ignored (notifications are minimized at all times).
383 if (notification_data
.require_interaction
)
384 notification
.set_never_timeout(true);
389 NotificationUIManager
*
390 PlatformNotificationServiceImpl::GetNotificationUIManager() const {
391 if (notification_ui_manager_for_tests_
)
392 return notification_ui_manager_for_tests_
;
394 return g_browser_process
->notification_ui_manager();
397 bool PlatformNotificationServiceImpl::OpenNotificationSettings(
398 BrowserContext
* browser_context
) {
399 #if !defined(OS_ANDROID)
400 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
401 switches::kNotificationSettingsButton
)) {
402 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
404 chrome::ScopedTabbedBrowserDisplayer
browser_displayer(
405 profile
, chrome::GetActiveDesktop());
406 chrome::ShowContentSettingsExceptions(browser_displayer
.browser(),
407 CONTENT_SETTINGS_TYPE_NOTIFICATIONS
);
410 #endif // !defined(OS_ANDROID)
414 void PlatformNotificationServiceImpl::SetNotificationUIManagerForTesting(
415 NotificationUIManager
* manager
) {
416 notification_ui_manager_for_tests_
= manager
;
419 base::string16
PlatformNotificationServiceImpl::DisplayNameForContextMessage(
421 const GURL
& origin
) const {
422 #if defined(ENABLE_EXTENSIONS)
423 // If the source is an extension, lookup the display name.
424 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
425 const extensions::Extension
* extension
=
426 extensions::ExtensionRegistry::Get(profile
)->GetExtensionById(
427 origin
.host(), extensions::ExtensionRegistry::EVERYTHING
);
430 return base::UTF8ToUTF16(extension
->name());
434 return base::string16();