1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
7 #include "base/callback.h"
9 #include "base/rand_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/time/time.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/notifications/desktop_notification_service.h"
15 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
16 #include "chrome/browser/notifications/notification.h"
17 #include "chrome/browser/notifications/notification_conversion_helper.h"
18 #include "chrome/browser/notifications/notification_ui_manager.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/common/chrome_version_info.h"
21 #include "chrome/common/extensions/api/notifications/notification_style.h"
22 #include "content/public/browser/render_process_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "extensions/browser/event_router.h"
26 #include "extensions/common/extension.h"
27 #include "extensions/common/features/feature.h"
28 #include "third_party/skia/include/core/SkBitmap.h"
29 #include "ui/base/layout.h"
30 #include "ui/gfx/image/image.h"
31 #include "ui/gfx/image/image_skia.h"
32 #include "ui/gfx/image/image_skia_rep.h"
33 #include "ui/message_center/message_center_style.h"
34 #include "ui/message_center/notifier_settings.h"
37 namespace extensions
{
39 namespace notifications
= api::notifications
;
43 const char kMissingRequiredPropertiesForCreateNotification
[] =
44 "Some of the required properties are missing: type, iconUrl, title and "
46 const char kUnableToDecodeIconError
[] =
47 "Unable to successfully use the provided image.";
48 const char kUnexpectedProgressValueForNonProgressType
[] =
49 "The progress value should not be specified for non-progress notification";
50 const char kInvalidProgressValue
[] =
51 "The progress value should range from 0 to 100";
52 const char kExtraListItemsProvided
[] =
53 "List items provided for notification type != list";
54 const char kExtraImageProvided
[] =
55 "Image resource provided for notification type != image";
57 // Given an extension id and another id, returns an id that is unique
58 // relative to other extensions.
59 std::string
CreateScopedIdentifier(const std::string
& extension_id
,
60 const std::string
& id
) {
61 return extension_id
+ "-" + id
;
64 // Removes the unique internal identifier to send the ID as the
65 // extension expects it.
66 std::string
StripScopeFromIdentifier(const std::string
& extension_id
,
67 const std::string
& id
) {
68 size_t index_of_separator
= extension_id
.length() + 1;
69 DCHECK_LT(index_of_separator
, id
.length());
71 return id
.substr(index_of_separator
);
74 class NotificationsApiDelegate
: public NotificationDelegate
{
76 NotificationsApiDelegate(ChromeAsyncExtensionFunction
* api_function
,
78 const std::string
& extension_id
,
79 const std::string
& id
)
80 : api_function_(api_function
),
82 extension_id_(extension_id
),
84 scoped_id_(CreateScopedIdentifier(extension_id
, id
)) {
85 DCHECK(api_function_
.get());
88 void Close(bool by_user
) override
{
89 EventRouter::UserGestureState gesture
=
90 by_user
? EventRouter::USER_GESTURE_ENABLED
91 : EventRouter::USER_GESTURE_NOT_ENABLED
;
92 scoped_ptr
<base::ListValue
> args(CreateBaseEventArgs());
93 args
->Append(new base::FundamentalValue(by_user
));
94 SendEvent(notifications::OnClosed::kEventName
, gesture
, args
.Pass());
97 void Click() override
{
98 scoped_ptr
<base::ListValue
> args(CreateBaseEventArgs());
99 SendEvent(notifications::OnClicked::kEventName
,
100 EventRouter::USER_GESTURE_ENABLED
,
104 bool HasClickedListener() override
{
105 return EventRouter::Get(profile_
)->HasEventListener(
106 notifications::OnClicked::kEventName
);
109 void ButtonClick(int index
) override
{
110 scoped_ptr
<base::ListValue
> args(CreateBaseEventArgs());
111 args
->Append(new base::FundamentalValue(index
));
112 SendEvent(notifications::OnButtonClicked::kEventName
,
113 EventRouter::USER_GESTURE_ENABLED
,
117 std::string
id() const override
{ return scoped_id_
; }
120 ~NotificationsApiDelegate() override
{}
122 void SendEvent(const std::string
& name
,
123 EventRouter::UserGestureState user_gesture
,
124 scoped_ptr
<base::ListValue
> args
) {
125 scoped_ptr
<Event
> event(new Event(name
, args
.Pass()));
126 event
->user_gesture
= user_gesture
;
127 EventRouter::Get(profile_
)->DispatchEventToExtension(extension_id_
,
131 scoped_ptr
<base::ListValue
> CreateBaseEventArgs() {
132 scoped_ptr
<base::ListValue
> args(new base::ListValue());
133 args
->Append(new base::StringValue(id_
));
137 scoped_refptr
<ChromeAsyncExtensionFunction
> api_function_
;
139 const std::string extension_id_
;
140 const std::string id_
;
141 const std::string scoped_id_
;
143 DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate
);
148 bool NotificationsApiFunction::IsNotificationsApiAvailable() {
149 // We need to check this explicitly rather than letting
150 // _permission_features.json enforce it, because we're sharing the
151 // chrome.notifications permissions namespace with WebKit notifications.
152 return extension()->is_platform_app() || extension()->is_extension();
155 NotificationsApiFunction::NotificationsApiFunction() {
158 NotificationsApiFunction::~NotificationsApiFunction() {
161 bool NotificationsApiFunction::CreateNotification(
162 const std::string
& id
,
163 api::notifications::NotificationOptions
* options
) {
164 // First, make sure the required fields exist: type, title, message, icon.
165 // These fields are defined as optional in IDL such that they can be used as
166 // optional for notification updates. But for notification creations, they
167 // should be present.
168 if (options
->type
== api::notifications::TEMPLATE_TYPE_NONE
||
169 !options
->icon_url
|| !options
->title
|| !options
->message
) {
170 SetError(kMissingRequiredPropertiesForCreateNotification
);
174 NotificationBitmapSizes bitmap_sizes
= GetNotificationBitmapSizes();
177 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
179 // Extract required fields: type, title, message, and icon.
180 message_center::NotificationType type
=
181 MapApiTemplateTypeToType(options
->type
);
182 const base::string16
title(base::UTF8ToUTF16(*options
->title
));
183 const base::string16
message(base::UTF8ToUTF16(*options
->message
));
186 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
188 bitmap_sizes
.icon_size
,
189 options
->icon_bitmap
.get(),
191 SetError(kUnableToDecodeIconError
);
195 // Then, handle any optional data that's been provided.
196 message_center::RichNotificationData optional_fields
;
197 if (options
->app_icon_mask_url
.get()) {
198 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
200 bitmap_sizes
.app_icon_mask_size
,
201 options
->app_icon_mask_bitmap
.get(),
202 &optional_fields
.small_image
)) {
203 SetError(kUnableToDecodeIconError
);
208 if (options
->priority
.get())
209 optional_fields
.priority
= *options
->priority
;
211 if (options
->event_time
.get())
212 optional_fields
.timestamp
= base::Time::FromJsTime(*options
->event_time
);
214 if (options
->buttons
.get()) {
215 // Currently we allow up to 2 buttons.
216 size_t number_of_buttons
= options
->buttons
->size();
217 number_of_buttons
= number_of_buttons
> 2 ? 2 : number_of_buttons
;
219 for (size_t i
= 0; i
< number_of_buttons
; i
++) {
220 message_center::ButtonInfo
info(
221 base::UTF8ToUTF16((*options
->buttons
)[i
]->title
));
222 NotificationConversionHelper::NotificationBitmapToGfxImage(
224 bitmap_sizes
.button_icon_size
,
225 (*options
->buttons
)[i
]->icon_bitmap
.get(),
227 optional_fields
.buttons
.push_back(info
);
231 if (options
->context_message
) {
232 optional_fields
.context_message
=
233 base::UTF8ToUTF16(*options
->context_message
);
236 bool has_image
= NotificationConversionHelper::NotificationBitmapToGfxImage(
238 bitmap_sizes
.image_size
,
239 options
->image_bitmap
.get(),
240 &optional_fields
.image
);
241 // We should have an image if and only if the type is an image type.
242 if (has_image
!= (type
== message_center::NOTIFICATION_TYPE_IMAGE
)) {
243 SetError(kExtraImageProvided
);
247 // We should have list items if and only if the type is a multiple type.
248 bool has_list_items
= options
->items
.get() && options
->items
->size() > 0;
249 if (has_list_items
!= (type
== message_center::NOTIFICATION_TYPE_MULTIPLE
)) {
250 SetError(kExtraListItemsProvided
);
254 if (options
->progress
.get() != NULL
) {
255 // We should have progress if and only if the type is a progress type.
256 if (type
!= message_center::NOTIFICATION_TYPE_PROGRESS
) {
257 SetError(kUnexpectedProgressValueForNonProgressType
);
260 optional_fields
.progress
= *options
->progress
;
261 // Progress value should range from 0 to 100.
262 if (optional_fields
.progress
< 0 || optional_fields
.progress
> 100) {
263 SetError(kInvalidProgressValue
);
268 if (has_list_items
) {
269 using api::notifications::NotificationItem
;
270 std::vector
<linked_ptr
<NotificationItem
> >::iterator i
;
271 for (i
= options
->items
->begin(); i
!= options
->items
->end(); ++i
) {
272 message_center::NotificationItem
item(
273 base::UTF8ToUTF16(i
->get()->title
),
274 base::UTF8ToUTF16(i
->get()->message
));
275 optional_fields
.items
.push_back(item
);
279 if (options
->is_clickable
.get())
280 optional_fields
.clickable
= *options
->is_clickable
;
282 NotificationsApiDelegate
* api_delegate(new NotificationsApiDelegate(
283 this, GetProfile(), extension_
->id(), id
)); // ownership is passed to
285 Notification
notification(type
,
290 message_center::NotifierId(
291 message_center::NotifierId::APPLICATION
,
293 base::UTF8ToUTF16(extension_
->name()),
298 g_browser_process
->notification_ui_manager()->Add(notification
, GetProfile());
302 bool NotificationsApiFunction::UpdateNotification(
303 const std::string
& id
,
304 api::notifications::NotificationOptions
* options
,
305 Notification
* notification
) {
306 NotificationBitmapSizes bitmap_sizes
= GetNotificationBitmapSizes();
308 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
310 // Update optional fields if provided.
311 if (options
->type
!= api::notifications::TEMPLATE_TYPE_NONE
)
312 notification
->set_type(MapApiTemplateTypeToType(options
->type
));
314 notification
->set_title(base::UTF8ToUTF16(*options
->title
));
315 if (options
->message
)
316 notification
->set_message(base::UTF8ToUTF16(*options
->message
));
318 // TODO(dewittj): Return error if this fails.
319 if (options
->icon_bitmap
) {
321 NotificationConversionHelper::NotificationBitmapToGfxImage(
322 image_scale
, bitmap_sizes
.icon_size
, options
->icon_bitmap
.get(), &icon
);
323 notification
->set_icon(icon
);
326 gfx::Image app_icon_mask
;
327 if (NotificationConversionHelper::NotificationBitmapToGfxImage(
329 bitmap_sizes
.app_icon_mask_size
,
330 options
->app_icon_mask_bitmap
.get(),
332 notification
->set_small_image(app_icon_mask
);
335 if (options
->priority
)
336 notification
->set_priority(*options
->priority
);
338 if (options
->event_time
)
339 notification
->set_timestamp(base::Time::FromJsTime(*options
->event_time
));
341 if (options
->buttons
) {
342 // Currently we allow up to 2 buttons.
343 size_t number_of_buttons
= options
->buttons
->size();
344 number_of_buttons
= number_of_buttons
> 2 ? 2 : number_of_buttons
;
346 std::vector
<message_center::ButtonInfo
> buttons
;
347 for (size_t i
= 0; i
< number_of_buttons
; i
++) {
348 message_center::ButtonInfo
button(
349 base::UTF8ToUTF16((*options
->buttons
)[i
]->title
));
350 NotificationConversionHelper::NotificationBitmapToGfxImage(
352 bitmap_sizes
.button_icon_size
,
353 (*options
->buttons
)[i
]->icon_bitmap
.get(),
355 buttons
.push_back(button
);
357 notification
->set_buttons(buttons
);
360 if (options
->context_message
) {
361 notification
->set_context_message(
362 base::UTF8ToUTF16(*options
->context_message
));
366 bool has_image
= NotificationConversionHelper::NotificationBitmapToGfxImage(
368 bitmap_sizes
.image_size
,
369 options
->image_bitmap
.get(),
372 // We should have an image if and only if the type is an image type.
373 if (notification
->type() != message_center::NOTIFICATION_TYPE_IMAGE
) {
374 SetError(kExtraImageProvided
);
377 notification
->set_image(image
);
380 if (options
->progress
) {
381 // We should have progress if and only if the type is a progress type.
382 if (notification
->type() != message_center::NOTIFICATION_TYPE_PROGRESS
) {
383 SetError(kUnexpectedProgressValueForNonProgressType
);
386 int progress
= *options
->progress
;
387 // Progress value should range from 0 to 100.
388 if (progress
< 0 || progress
> 100) {
389 SetError(kInvalidProgressValue
);
392 notification
->set_progress(progress
);
395 if (options
->items
.get() && options
->items
->size() > 0) {
396 // We should have list items if and only if the type is a multiple type.
397 if (notification
->type() != message_center::NOTIFICATION_TYPE_MULTIPLE
) {
398 SetError(kExtraListItemsProvided
);
402 std::vector
<message_center::NotificationItem
> items
;
403 using api::notifications::NotificationItem
;
404 std::vector
<linked_ptr
<NotificationItem
> >::iterator i
;
405 for (i
= options
->items
->begin(); i
!= options
->items
->end(); ++i
) {
406 message_center::NotificationItem
item(
407 base::UTF8ToUTF16(i
->get()->title
),
408 base::UTF8ToUTF16(i
->get()->message
));
409 items
.push_back(item
);
411 notification
->set_items(items
);
414 // Then override if it's already set.
415 if (options
->is_clickable
.get())
416 notification
->set_clickable(*options
->is_clickable
);
418 g_browser_process
->notification_ui_manager()->Update(*notification
,
423 bool NotificationsApiFunction::AreExtensionNotificationsAllowed() const {
424 DesktopNotificationService
* service
=
425 DesktopNotificationServiceFactory::GetForProfile(GetProfile());
426 return service
->IsNotifierEnabled(message_center::NotifierId(
427 message_center::NotifierId::APPLICATION
, extension_
->id()));
430 bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
431 return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
434 bool NotificationsApiFunction::CanRunWhileDisabled() const {
438 bool NotificationsApiFunction::RunAsync() {
439 if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
440 return RunNotificationsApi();
447 message_center::NotificationType
448 NotificationsApiFunction::MapApiTemplateTypeToType(
449 api::notifications::TemplateType type
) {
451 case api::notifications::TEMPLATE_TYPE_NONE
:
452 case api::notifications::TEMPLATE_TYPE_BASIC
:
453 return message_center::NOTIFICATION_TYPE_BASE_FORMAT
;
454 case api::notifications::TEMPLATE_TYPE_IMAGE
:
455 return message_center::NOTIFICATION_TYPE_IMAGE
;
456 case api::notifications::TEMPLATE_TYPE_LIST
:
457 return message_center::NOTIFICATION_TYPE_MULTIPLE
;
458 case api::notifications::TEMPLATE_TYPE_PROGRESS
:
459 return message_center::NOTIFICATION_TYPE_PROGRESS
;
461 // Gracefully handle newer application code that is running on an older
462 // runtime that doesn't recognize the requested template.
463 return message_center::NOTIFICATION_TYPE_BASE_FORMAT
;
467 NotificationsCreateFunction::NotificationsCreateFunction() {
470 NotificationsCreateFunction::~NotificationsCreateFunction() {
473 bool NotificationsCreateFunction::RunNotificationsApi() {
474 params_
= api::notifications::Create::Params::Create(*args_
);
475 EXTENSION_FUNCTION_VALIDATE(params_
.get());
477 const std::string
extension_id(extension_
->id());
478 std::string notification_id
;
479 if (params_
->notification_id
.get() && !params_
->notification_id
->empty()) {
480 // If the caller provided a notificationId, use that.
481 notification_id
= *params_
->notification_id
;
483 // Otherwise, use a randomly created GUID. In case that GenerateGUID returns
484 // the empty string, simply generate a random string.
485 notification_id
= base::GenerateGUID();
486 if (notification_id
.empty())
487 notification_id
= base::RandBytesAsString(16);
490 SetResult(new base::StringValue(notification_id
));
492 // TODO(dewittj): Add more human-readable error strings if this fails.
493 if (!CreateNotification(notification_id
, ¶ms_
->options
))
501 NotificationsUpdateFunction::NotificationsUpdateFunction() {
504 NotificationsUpdateFunction::~NotificationsUpdateFunction() {
507 bool NotificationsUpdateFunction::RunNotificationsApi() {
508 params_
= api::notifications::Update::Params::Create(*args_
);
509 EXTENSION_FUNCTION_VALIDATE(params_
.get());
511 // We are in update. If the ID doesn't exist, succeed but call the callback
513 const Notification
* matched_notification
=
514 g_browser_process
->notification_ui_manager()->FindById(
515 CreateScopedIdentifier(extension_
->id(), params_
->notification_id
),
516 NotificationUIManager::GetProfileID(GetProfile()));
517 if (!matched_notification
) {
518 SetResult(new base::FundamentalValue(false));
523 // Copy the existing notification to get a writable version of it.
524 Notification notification
= *matched_notification
;
526 // If we have trouble updating the notification (could be improper use of API
527 // or some other reason), mark the function as failed, calling the callback
529 // TODO(dewittj): Add more human-readable error strings if this fails.
530 bool could_update_notification
= UpdateNotification(
531 params_
->notification_id
, ¶ms_
->options
, ¬ification
);
532 SetResult(new base::FundamentalValue(could_update_notification
));
533 if (!could_update_notification
)
536 // No trouble, created the notification, send true to the callback and
542 NotificationsClearFunction::NotificationsClearFunction() {
545 NotificationsClearFunction::~NotificationsClearFunction() {
548 bool NotificationsClearFunction::RunNotificationsApi() {
549 params_
= api::notifications::Clear::Params::Create(*args_
);
550 EXTENSION_FUNCTION_VALIDATE(params_
.get());
552 bool cancel_result
= g_browser_process
->notification_ui_manager()->CancelById(
553 CreateScopedIdentifier(extension_
->id(), params_
->notification_id
),
554 NotificationUIManager::GetProfileID(GetProfile()));
556 SetResult(new base::FundamentalValue(cancel_result
));
562 NotificationsGetAllFunction::NotificationsGetAllFunction() {}
564 NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
566 bool NotificationsGetAllFunction::RunNotificationsApi() {
567 NotificationUIManager
* notification_ui_manager
=
568 g_browser_process
->notification_ui_manager();
569 std::set
<std::string
> notification_ids
=
570 notification_ui_manager
->GetAllIdsByProfileAndSourceOrigin(
571 GetProfile(), extension_
->url());
573 scoped_ptr
<base::DictionaryValue
> result(new base::DictionaryValue());
575 for (std::set
<std::string
>::iterator iter
= notification_ids
.begin();
576 iter
!= notification_ids
.end(); iter
++) {
577 result
->SetBooleanWithoutPathExpansion(
578 StripScopeFromIdentifier(extension_
->id(), *iter
), true);
581 SetResult(result
.release());
587 NotificationsGetPermissionLevelFunction::
588 NotificationsGetPermissionLevelFunction() {}
590 NotificationsGetPermissionLevelFunction::
591 ~NotificationsGetPermissionLevelFunction() {}
593 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
597 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() {
598 api::notifications::PermissionLevel result
=
599 AreExtensionNotificationsAllowed()
600 ? api::notifications::PERMISSION_LEVEL_GRANTED
601 : api::notifications::PERMISSION_LEVEL_DENIED
;
603 SetResult(new base::StringValue(api::notifications::ToString(result
)));
609 } // namespace extensions