Add a Notification Settings Button to all web notifications behind the web platform...
[chromium-blink-merge.git] / chrome / browser / notifications / platform_notification_service_impl.cc
blob5db2c74a1a00b049c0f8c7bea8bea2f8a1d08af5
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"
49 #endif
51 #if defined(OS_ANDROID)
52 #include "base/strings/string_number_conversions.h"
53 #endif
55 using content::BrowserContext;
56 using content::BrowserThread;
57 using content::PlatformNotificationContext;
58 using message_center::NotifierId;
60 namespace {
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",
66 success);
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);
83 } // namespace
85 // static
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,
99 const GURL& origin,
100 int action_index) const {
101 DCHECK_CURRENTLY_ON(BrowserThread::UI);
102 content::NotificationEventDispatcher::GetInstance()
103 ->DispatchNotificationClickEvent(
104 browser_context,
105 persistent_notification_id,
106 origin,
107 action_index,
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(
121 BrowserThread::IO,
122 FROM_HERE,
123 base::Bind(&PlatformNotificationContext::DeleteNotificationData,
124 context,
125 persistent_notification_id,
126 origin,
127 base::Bind(&OnPersistentNotificationDataDeleted)));
130 blink::WebNotificationPermission
131 PlatformNotificationServiceImpl::CheckPermissionOnUIThread(
132 BrowserContext* browser_context,
133 const GURL& origin,
134 int render_process_id) {
135 DCHECK_CURRENTLY_ON(BrowserThread::UI);
137 Profile* profile = Profile::FromBrowserContext(browser_context);
138 DCHECK(profile);
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);
154 if (extension &&
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;
167 #endif
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,
183 const GURL& origin,
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());
200 if (extension &&
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;
208 #endif
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(
214 origin,
215 origin,
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,
229 const GURL& origin,
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);
237 DCHECK(profile);
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);
246 if (cancel_callback)
247 *cancel_callback =
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,
259 const GURL& origin,
260 const SkBitmap& icon,
261 const content::PlatformNotificationData& notification_data) {
262 DCHECK_CURRENTLY_ON(BrowserThread::UI);
264 Profile* profile = Profile::FromBrowserContext(browser_context);
265 DCHECK(profile);
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);
293 DCHECK(profile);
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));
303 #else
304 auto iter = persistent_notifications_.find(persistent_notification_id);
305 if (iter == persistent_notifications_.end())
306 return;
308 GetNotificationUIManager()->CancelById(
309 iter->second, NotificationUIManager::GetProfileID(profile));
311 persistent_notifications_.erase(iter);
312 #endif
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));
329 return true;
330 #else
331 // Android cannot reliably return the notifications that are currently being
332 // displayed on the platform, see the comment in NotificationUIManagerAndroid.
333 return false;
334 #endif // !defined(OS_ANDROID)
337 Notification PlatformNotificationServiceImpl::CreateNotificationFromData(
338 Profile* profile,
339 const GURL& origin,
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);
386 return notification;
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);
403 DCHECK(profile);
404 chrome::ScopedTabbedBrowserDisplayer browser_displayer(
405 profile, chrome::GetActiveDesktop());
406 chrome::ShowContentSettingsExceptions(browser_displayer.browser(),
407 CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
408 return true;
410 #endif // !defined(OS_ANDROID)
411 return false;
414 void PlatformNotificationServiceImpl::SetNotificationUIManagerForTesting(
415 NotificationUIManager* manager) {
416 notification_ui_manager_for_tests_ = manager;
419 base::string16 PlatformNotificationServiceImpl::DisplayNameForContextMessage(
420 Profile* profile,
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);
428 DCHECK(extension);
430 return base::UTF8ToUTF16(extension->name());
432 #endif
434 return base::string16();