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 blink::WebTextDirectionDefault,
291 message_center::NotifierId(
292 message_center::NotifierId::APPLICATION,
294 base::UTF8ToUTF16(extension_->name()),
295 base::UTF8ToUTF16(api_delegate->id()),
299 g_browser_process->notification_ui_manager()->Add(notification, GetProfile());
303 bool NotificationsApiFunction::UpdateNotification(
304 const std::string& id,
305 api::notifications::NotificationOptions* options,
306 Notification* notification) {
307 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
309 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
311 // Update optional fields if provided.
312 if (options->type != api::notifications::TEMPLATE_TYPE_NONE)
313 notification->set_type(MapApiTemplateTypeToType(options->type));
315 notification->set_title(base::UTF8ToUTF16(*options->title));
316 if (options->message)
317 notification->set_message(base::UTF8ToUTF16(*options->message));
319 // TODO(dewittj): Return error if this fails.
320 if (options->icon_bitmap) {
322 NotificationConversionHelper::NotificationBitmapToGfxImage(
323 image_scale, bitmap_sizes.icon_size, options->icon_bitmap.get(), &icon);
324 notification->set_icon(icon);
327 gfx::Image app_icon_mask;
328 if (NotificationConversionHelper::NotificationBitmapToGfxImage(
330 bitmap_sizes.app_icon_mask_size,
331 options->app_icon_mask_bitmap.get(),
333 notification->set_small_image(app_icon_mask);
336 if (options->priority)
337 notification->set_priority(*options->priority);
339 if (options->event_time)
340 notification->set_timestamp(base::Time::FromJsTime(*options->event_time));
342 if (options->buttons) {
343 // Currently we allow up to 2 buttons.
344 size_t number_of_buttons = options->buttons->size();
345 number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons;
347 std::vector<message_center::ButtonInfo> buttons;
348 for (size_t i = 0; i < number_of_buttons; i++) {
349 message_center::ButtonInfo button(
350 base::UTF8ToUTF16((*options->buttons)[i]->title));
351 NotificationConversionHelper::NotificationBitmapToGfxImage(
353 bitmap_sizes.button_icon_size,
354 (*options->buttons)[i]->icon_bitmap.get(),
356 buttons.push_back(button);
358 notification->set_buttons(buttons);
361 if (options->context_message) {
362 notification->set_context_message(
363 base::UTF8ToUTF16(*options->context_message));
367 bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage(
369 bitmap_sizes.image_size,
370 options->image_bitmap.get(),
373 // We should have an image if and only if the type is an image type.
374 if (notification->type() != message_center::NOTIFICATION_TYPE_IMAGE) {
375 SetError(kExtraImageProvided);
378 notification->set_image(image);
381 if (options->progress) {
382 // We should have progress if and only if the type is a progress type.
383 if (notification->type() != message_center::NOTIFICATION_TYPE_PROGRESS) {
384 SetError(kUnexpectedProgressValueForNonProgressType);
387 int progress = *options->progress;
388 // Progress value should range from 0 to 100.
389 if (progress < 0 || progress > 100) {
390 SetError(kInvalidProgressValue);
393 notification->set_progress(progress);
396 if (options->items.get() && options->items->size() > 0) {
397 // We should have list items if and only if the type is a multiple type.
398 if (notification->type() != message_center::NOTIFICATION_TYPE_MULTIPLE) {
399 SetError(kExtraListItemsProvided);
403 std::vector<message_center::NotificationItem> items;
404 using api::notifications::NotificationItem;
405 std::vector<linked_ptr<NotificationItem> >::iterator i;
406 for (i = options->items->begin(); i != options->items->end(); ++i) {
407 message_center::NotificationItem item(
408 base::UTF8ToUTF16(i->get()->title),
409 base::UTF8ToUTF16(i->get()->message));
410 items.push_back(item);
412 notification->set_items(items);
415 // Then override if it's already set.
416 if (options->is_clickable.get())
417 notification->set_clickable(*options->is_clickable);
419 g_browser_process->notification_ui_manager()->Update(*notification,
424 bool NotificationsApiFunction::AreExtensionNotificationsAllowed() const {
425 DesktopNotificationService* service =
426 DesktopNotificationServiceFactory::GetForProfile(GetProfile());
427 return service->IsNotifierEnabled(message_center::NotifierId(
428 message_center::NotifierId::APPLICATION, extension_->id()));
431 bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
432 return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
435 bool NotificationsApiFunction::CanRunWhileDisabled() const {
439 bool NotificationsApiFunction::RunAsync() {
440 if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
441 return RunNotificationsApi();
448 message_center::NotificationType
449 NotificationsApiFunction::MapApiTemplateTypeToType(
450 api::notifications::TemplateType type) {
452 case api::notifications::TEMPLATE_TYPE_NONE:
453 case api::notifications::TEMPLATE_TYPE_BASIC:
454 return message_center::NOTIFICATION_TYPE_BASE_FORMAT;
455 case api::notifications::TEMPLATE_TYPE_IMAGE:
456 return message_center::NOTIFICATION_TYPE_IMAGE;
457 case api::notifications::TEMPLATE_TYPE_LIST:
458 return message_center::NOTIFICATION_TYPE_MULTIPLE;
459 case api::notifications::TEMPLATE_TYPE_PROGRESS:
460 return message_center::NOTIFICATION_TYPE_PROGRESS;
462 // Gracefully handle newer application code that is running on an older
463 // runtime that doesn't recognize the requested template.
464 return message_center::NOTIFICATION_TYPE_BASE_FORMAT;
468 NotificationsCreateFunction::NotificationsCreateFunction() {
471 NotificationsCreateFunction::~NotificationsCreateFunction() {
474 bool NotificationsCreateFunction::RunNotificationsApi() {
475 params_ = api::notifications::Create::Params::Create(*args_);
476 EXTENSION_FUNCTION_VALIDATE(params_.get());
478 const std::string extension_id(extension_->id());
479 std::string notification_id;
480 if (!params_->notification_id.empty()) {
481 // If the caller provided a notificationId, use that.
482 notification_id = params_->notification_id;
484 // Otherwise, use a randomly created GUID. In case that GenerateGUID returns
485 // the empty string, simply generate a random string.
486 notification_id = base::GenerateGUID();
487 if (notification_id.empty())
488 notification_id = base::RandBytesAsString(16);
491 SetResult(new base::StringValue(notification_id));
493 // TODO(dewittj): Add more human-readable error strings if this fails.
494 if (!CreateNotification(notification_id, ¶ms_->options))
502 NotificationsUpdateFunction::NotificationsUpdateFunction() {
505 NotificationsUpdateFunction::~NotificationsUpdateFunction() {
508 bool NotificationsUpdateFunction::RunNotificationsApi() {
509 params_ = api::notifications::Update::Params::Create(*args_);
510 EXTENSION_FUNCTION_VALIDATE(params_.get());
512 // We are in update. If the ID doesn't exist, succeed but call the callback
514 const Notification* matched_notification =
515 g_browser_process->notification_ui_manager()->FindById(
516 CreateScopedIdentifier(extension_->id(), params_->notification_id),
517 NotificationUIManager::GetProfileID(GetProfile()));
518 if (!matched_notification) {
519 SetResult(new base::FundamentalValue(false));
524 // Copy the existing notification to get a writable version of it.
525 Notification notification = *matched_notification;
527 // If we have trouble updating the notification (could be improper use of API
528 // or some other reason), mark the function as failed, calling the callback
530 // TODO(dewittj): Add more human-readable error strings if this fails.
531 bool could_update_notification = UpdateNotification(
532 params_->notification_id, ¶ms_->options, ¬ification);
533 SetResult(new base::FundamentalValue(could_update_notification));
534 if (!could_update_notification)
537 // No trouble, created the notification, send true to the callback and
543 NotificationsClearFunction::NotificationsClearFunction() {
546 NotificationsClearFunction::~NotificationsClearFunction() {
549 bool NotificationsClearFunction::RunNotificationsApi() {
550 params_ = api::notifications::Clear::Params::Create(*args_);
551 EXTENSION_FUNCTION_VALIDATE(params_.get());
553 bool cancel_result = g_browser_process->notification_ui_manager()->CancelById(
554 CreateScopedIdentifier(extension_->id(), params_->notification_id),
555 NotificationUIManager::GetProfileID(GetProfile()));
557 SetResult(new base::FundamentalValue(cancel_result));
563 NotificationsGetAllFunction::NotificationsGetAllFunction() {}
565 NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
567 bool NotificationsGetAllFunction::RunNotificationsApi() {
568 NotificationUIManager* notification_ui_manager =
569 g_browser_process->notification_ui_manager();
570 std::set<std::string> notification_ids =
571 notification_ui_manager->GetAllIdsByProfileAndSourceOrigin(
572 GetProfile(), extension_->url());
574 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
576 for (std::set<std::string>::iterator iter = notification_ids.begin();
577 iter != notification_ids.end(); iter++) {
578 result->SetBooleanWithoutPathExpansion(
579 StripScopeFromIdentifier(extension_->id(), *iter), true);
582 SetResult(result.release());
588 NotificationsGetPermissionLevelFunction::
589 NotificationsGetPermissionLevelFunction() {}
591 NotificationsGetPermissionLevelFunction::
592 ~NotificationsGetPermissionLevelFunction() {}
594 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
598 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() {
599 api::notifications::PermissionLevel result =
600 AreExtensionNotificationsAllowed()
601 ? api::notifications::PERMISSION_LEVEL_GRANTED
602 : api::notifications::PERMISSION_LEVEL_DENIED;
604 SetResult(new base::StringValue(api::notifications::ToString(result)));
610 } // namespace extensions