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 virtual void Display() OVERRIDE { }
90 virtual void Error() OVERRIDE {}
92 virtual void Close(bool by_user) OVERRIDE {
93 EventRouter::UserGestureState gesture =
94 by_user ? EventRouter::USER_GESTURE_ENABLED
95 : EventRouter::USER_GESTURE_NOT_ENABLED;
96 scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
97 args->Append(new base::FundamentalValue(by_user));
98 SendEvent(notifications::OnClosed::kEventName, gesture, args.Pass());
101 virtual void Click() OVERRIDE {
102 scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
103 SendEvent(notifications::OnClicked::kEventName,
104 EventRouter::USER_GESTURE_ENABLED,
108 virtual bool HasClickedListener() OVERRIDE {
109 return EventRouter::Get(profile_)->HasEventListener(
110 notifications::OnClicked::kEventName);
113 virtual void ButtonClick(int index) OVERRIDE {
114 scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
115 args->Append(new base::FundamentalValue(index));
116 SendEvent(notifications::OnButtonClicked::kEventName,
117 EventRouter::USER_GESTURE_ENABLED,
121 virtual std::string id() const OVERRIDE {
125 virtual content::WebContents* GetWebContents() const OVERRIDE {
126 // We're holding a reference to api_function_, so we know it'll be valid
127 // until ReleaseRVH is called, and api_function_ (as a
128 // AsyncExtensionFunction) will zero out its copy of render_view_host
129 // when the RVH goes away.
130 if (!api_function_.get())
132 content::RenderViewHost* rvh = api_function_->render_view_host();
135 return content::WebContents::FromRenderViewHost(rvh);
138 virtual void ReleaseRenderViewHost() OVERRIDE {
139 api_function_ = NULL;
143 virtual ~NotificationsApiDelegate() {}
145 void SendEvent(const std::string& name,
146 EventRouter::UserGestureState user_gesture,
147 scoped_ptr<base::ListValue> args) {
148 scoped_ptr<Event> event(new Event(name, args.Pass()));
149 event->user_gesture = user_gesture;
150 EventRouter::Get(profile_)->DispatchEventToExtension(extension_id_,
154 scoped_ptr<base::ListValue> CreateBaseEventArgs() {
155 scoped_ptr<base::ListValue> args(new base::ListValue());
156 args->Append(new base::StringValue(id_));
160 scoped_refptr<ChromeAsyncExtensionFunction> api_function_;
162 const std::string extension_id_;
163 const std::string id_;
164 const std::string scoped_id_;
166 DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate);
171 bool NotificationsApiFunction::IsNotificationsApiAvailable() {
172 // We need to check this explicitly rather than letting
173 // _permission_features.json enforce it, because we're sharing the
174 // chrome.notifications permissions namespace with WebKit notifications.
175 return extension()->is_platform_app() || extension()->is_extension();
178 NotificationsApiFunction::NotificationsApiFunction() {
181 NotificationsApiFunction::~NotificationsApiFunction() {
184 bool NotificationsApiFunction::CreateNotification(
185 const std::string& id,
186 api::notifications::NotificationOptions* options) {
187 // First, make sure the required fields exist: type, title, message, icon.
188 // These fields are defined as optional in IDL such that they can be used as
189 // optional for notification updates. But for notification creations, they
190 // should be present.
191 if (options->type == api::notifications::TEMPLATE_TYPE_NONE ||
192 !options->icon_url || !options->title || !options->message) {
193 SetError(kMissingRequiredPropertiesForCreateNotification);
197 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
200 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
202 // Extract required fields: type, title, message, and icon.
203 message_center::NotificationType type =
204 MapApiTemplateTypeToType(options->type);
205 const base::string16 title(base::UTF8ToUTF16(*options->title));
206 const base::string16 message(base::UTF8ToUTF16(*options->message));
209 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
211 bitmap_sizes.icon_size,
212 options->icon_bitmap.get(),
214 SetError(kUnableToDecodeIconError);
218 // Then, handle any optional data that's been provided.
219 message_center::RichNotificationData optional_fields;
220 if (options->app_icon_mask_url.get()) {
221 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
223 bitmap_sizes.app_icon_mask_size,
224 options->app_icon_mask_bitmap.get(),
225 &optional_fields.small_image)) {
226 SetError(kUnableToDecodeIconError);
231 if (options->priority.get())
232 optional_fields.priority = *options->priority;
234 if (options->event_time.get())
235 optional_fields.timestamp = base::Time::FromJsTime(*options->event_time);
237 if (options->buttons.get()) {
238 // Currently we allow up to 2 buttons.
239 size_t number_of_buttons = options->buttons->size();
240 number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons;
242 for (size_t i = 0; i < number_of_buttons; i++) {
243 message_center::ButtonInfo info(
244 base::UTF8ToUTF16((*options->buttons)[i]->title));
245 NotificationConversionHelper::NotificationBitmapToGfxImage(
247 bitmap_sizes.button_icon_size,
248 (*options->buttons)[i]->icon_bitmap.get(),
250 optional_fields.buttons.push_back(info);
254 if (options->context_message) {
255 optional_fields.context_message =
256 base::UTF8ToUTF16(*options->context_message);
259 bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage(
261 bitmap_sizes.image_size,
262 options->image_bitmap.get(),
263 &optional_fields.image);
264 // We should have an image if and only if the type is an image type.
265 if (has_image != (type == message_center::NOTIFICATION_TYPE_IMAGE)) {
266 SetError(kExtraImageProvided);
270 // We should have list items if and only if the type is a multiple type.
271 bool has_list_items = options->items.get() && options->items->size() > 0;
272 if (has_list_items != (type == message_center::NOTIFICATION_TYPE_MULTIPLE)) {
273 SetError(kExtraListItemsProvided);
277 if (options->progress.get() != NULL) {
278 // We should have progress if and only if the type is a progress type.
279 if (type != message_center::NOTIFICATION_TYPE_PROGRESS) {
280 SetError(kUnexpectedProgressValueForNonProgressType);
283 optional_fields.progress = *options->progress;
284 // Progress value should range from 0 to 100.
285 if (optional_fields.progress < 0 || optional_fields.progress > 100) {
286 SetError(kInvalidProgressValue);
291 if (has_list_items) {
292 using api::notifications::NotificationItem;
293 std::vector<linked_ptr<NotificationItem> >::iterator i;
294 for (i = options->items->begin(); i != options->items->end(); ++i) {
295 message_center::NotificationItem item(
296 base::UTF8ToUTF16(i->get()->title),
297 base::UTF8ToUTF16(i->get()->message));
298 optional_fields.items.push_back(item);
302 if (options->is_clickable.get())
303 optional_fields.clickable = *options->is_clickable;
305 NotificationsApiDelegate* api_delegate(new NotificationsApiDelegate(
306 this, GetProfile(), extension_->id(), id)); // ownership is passed to
308 Notification notification(type,
313 blink::WebTextDirectionDefault,
314 message_center::NotifierId(
315 message_center::NotifierId::APPLICATION,
317 base::UTF8ToUTF16(extension_->name()),
318 base::UTF8ToUTF16(api_delegate->id()),
322 g_browser_process->notification_ui_manager()->Add(notification, GetProfile());
326 bool NotificationsApiFunction::UpdateNotification(
327 const std::string& id,
328 api::notifications::NotificationOptions* options,
329 Notification* notification) {
330 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
332 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
334 // Update optional fields if provided.
335 if (options->type != api::notifications::TEMPLATE_TYPE_NONE)
336 notification->set_type(MapApiTemplateTypeToType(options->type));
338 notification->set_title(base::UTF8ToUTF16(*options->title));
339 if (options->message)
340 notification->set_message(base::UTF8ToUTF16(*options->message));
342 // TODO(dewittj): Return error if this fails.
343 if (options->icon_bitmap) {
345 NotificationConversionHelper::NotificationBitmapToGfxImage(
346 image_scale, bitmap_sizes.icon_size, options->icon_bitmap.get(), &icon);
347 notification->set_icon(icon);
350 gfx::Image app_icon_mask;
351 if (NotificationConversionHelper::NotificationBitmapToGfxImage(
353 bitmap_sizes.app_icon_mask_size,
354 options->app_icon_mask_bitmap.get(),
356 notification->set_small_image(app_icon_mask);
359 if (options->priority)
360 notification->set_priority(*options->priority);
362 if (options->event_time)
363 notification->set_timestamp(base::Time::FromJsTime(*options->event_time));
365 if (options->buttons) {
366 // Currently we allow up to 2 buttons.
367 size_t number_of_buttons = options->buttons->size();
368 number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons;
370 std::vector<message_center::ButtonInfo> buttons;
371 for (size_t i = 0; i < number_of_buttons; i++) {
372 message_center::ButtonInfo button(
373 base::UTF8ToUTF16((*options->buttons)[i]->title));
374 NotificationConversionHelper::NotificationBitmapToGfxImage(
376 bitmap_sizes.button_icon_size,
377 (*options->buttons)[i]->icon_bitmap.get(),
379 buttons.push_back(button);
381 notification->set_buttons(buttons);
384 if (options->context_message) {
385 notification->set_context_message(
386 base::UTF8ToUTF16(*options->context_message));
390 bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage(
392 bitmap_sizes.image_size,
393 options->image_bitmap.get(),
396 // We should have an image if and only if the type is an image type.
397 if (notification->type() != message_center::NOTIFICATION_TYPE_IMAGE) {
398 SetError(kExtraImageProvided);
401 notification->set_image(image);
404 if (options->progress) {
405 // We should have progress if and only if the type is a progress type.
406 if (notification->type() != message_center::NOTIFICATION_TYPE_PROGRESS) {
407 SetError(kUnexpectedProgressValueForNonProgressType);
410 int progress = *options->progress;
411 // Progress value should range from 0 to 100.
412 if (progress < 0 || progress > 100) {
413 SetError(kInvalidProgressValue);
416 notification->set_progress(progress);
419 if (options->items.get() && options->items->size() > 0) {
420 // We should have list items if and only if the type is a multiple type.
421 if (notification->type() != message_center::NOTIFICATION_TYPE_MULTIPLE) {
422 SetError(kExtraListItemsProvided);
426 std::vector<message_center::NotificationItem> items;
427 using api::notifications::NotificationItem;
428 std::vector<linked_ptr<NotificationItem> >::iterator i;
429 for (i = options->items->begin(); i != options->items->end(); ++i) {
430 message_center::NotificationItem item(
431 base::UTF8ToUTF16(i->get()->title),
432 base::UTF8ToUTF16(i->get()->message));
433 items.push_back(item);
435 notification->set_items(items);
438 // Then override if it's already set.
439 if (options->is_clickable.get())
440 notification->set_clickable(*options->is_clickable);
442 g_browser_process->notification_ui_manager()->Update(*notification,
447 bool NotificationsApiFunction::AreExtensionNotificationsAllowed() const {
448 DesktopNotificationService* service =
449 DesktopNotificationServiceFactory::GetForProfile(GetProfile());
450 return service->IsNotifierEnabled(message_center::NotifierId(
451 message_center::NotifierId::APPLICATION, extension_->id()));
454 bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
455 return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
458 bool NotificationsApiFunction::CanRunWhileDisabled() const {
462 bool NotificationsApiFunction::RunAsync() {
463 if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
464 return RunNotificationsApi();
471 message_center::NotificationType
472 NotificationsApiFunction::MapApiTemplateTypeToType(
473 api::notifications::TemplateType type) {
475 case api::notifications::TEMPLATE_TYPE_NONE:
476 case api::notifications::TEMPLATE_TYPE_BASIC:
477 return message_center::NOTIFICATION_TYPE_BASE_FORMAT;
478 case api::notifications::TEMPLATE_TYPE_IMAGE:
479 return message_center::NOTIFICATION_TYPE_IMAGE;
480 case api::notifications::TEMPLATE_TYPE_LIST:
481 return message_center::NOTIFICATION_TYPE_MULTIPLE;
482 case api::notifications::TEMPLATE_TYPE_PROGRESS:
483 return message_center::NOTIFICATION_TYPE_PROGRESS;
485 // Gracefully handle newer application code that is running on an older
486 // runtime that doesn't recognize the requested template.
487 return message_center::NOTIFICATION_TYPE_BASE_FORMAT;
491 NotificationsCreateFunction::NotificationsCreateFunction() {
494 NotificationsCreateFunction::~NotificationsCreateFunction() {
497 bool NotificationsCreateFunction::RunNotificationsApi() {
498 params_ = api::notifications::Create::Params::Create(*args_);
499 EXTENSION_FUNCTION_VALIDATE(params_.get());
501 const std::string extension_id(extension_->id());
502 std::string notification_id;
503 if (!params_->notification_id.empty()) {
504 // If the caller provided a notificationId, use that.
505 notification_id = params_->notification_id;
507 // Otherwise, use a randomly created GUID. In case that GenerateGUID returns
508 // the empty string, simply generate a random string.
509 notification_id = base::GenerateGUID();
510 if (notification_id.empty())
511 notification_id = base::RandBytesAsString(16);
514 SetResult(new base::StringValue(notification_id));
516 // TODO(dewittj): Add more human-readable error strings if this fails.
517 if (!CreateNotification(notification_id, ¶ms_->options))
525 NotificationsUpdateFunction::NotificationsUpdateFunction() {
528 NotificationsUpdateFunction::~NotificationsUpdateFunction() {
531 bool NotificationsUpdateFunction::RunNotificationsApi() {
532 params_ = api::notifications::Update::Params::Create(*args_);
533 EXTENSION_FUNCTION_VALIDATE(params_.get());
535 // We are in update. If the ID doesn't exist, succeed but call the callback
537 const Notification* matched_notification =
538 g_browser_process->notification_ui_manager()->FindById(
539 CreateScopedIdentifier(extension_->id(), params_->notification_id));
540 if (!matched_notification) {
541 SetResult(new base::FundamentalValue(false));
546 // Copy the existing notification to get a writable version of it.
547 Notification notification = *matched_notification;
549 // If we have trouble updating the notification (could be improper use of API
550 // or some other reason), mark the function as failed, calling the callback
552 // TODO(dewittj): Add more human-readable error strings if this fails.
553 bool could_update_notification = UpdateNotification(
554 params_->notification_id, ¶ms_->options, ¬ification);
555 SetResult(new base::FundamentalValue(could_update_notification));
556 if (!could_update_notification)
559 // No trouble, created the notification, send true to the callback and
565 NotificationsClearFunction::NotificationsClearFunction() {
568 NotificationsClearFunction::~NotificationsClearFunction() {
571 bool NotificationsClearFunction::RunNotificationsApi() {
572 params_ = api::notifications::Clear::Params::Create(*args_);
573 EXTENSION_FUNCTION_VALIDATE(params_.get());
575 bool cancel_result = g_browser_process->notification_ui_manager()->CancelById(
576 CreateScopedIdentifier(extension_->id(), params_->notification_id));
578 SetResult(new base::FundamentalValue(cancel_result));
584 NotificationsGetAllFunction::NotificationsGetAllFunction() {}
586 NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
588 bool NotificationsGetAllFunction::RunNotificationsApi() {
589 NotificationUIManager* notification_ui_manager =
590 g_browser_process->notification_ui_manager();
591 std::set<std::string> notification_ids =
592 notification_ui_manager->GetAllIdsByProfileAndSourceOrigin(
593 GetProfile(), extension_->url());
595 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
597 for (std::set<std::string>::iterator iter = notification_ids.begin();
598 iter != notification_ids.end(); iter++) {
599 result->SetBooleanWithoutPathExpansion(
600 StripScopeFromIdentifier(extension_->id(), *iter), true);
603 SetResult(result.release());
609 NotificationsGetPermissionLevelFunction::
610 NotificationsGetPermissionLevelFunction() {}
612 NotificationsGetPermissionLevelFunction::
613 ~NotificationsGetPermissionLevelFunction() {}
615 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
619 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() {
620 api::notifications::PermissionLevel result =
621 AreExtensionNotificationsAllowed()
622 ? api::notifications::PERMISSION_LEVEL_GRANTED
623 : api::notifications::PERMISSION_LEVEL_DENIED;
625 SetResult(new base::StringValue(api::notifications::ToString(result)));
631 } // namespace extensions