Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / extensions / api / notifications / notifications_api.cc
blob71fb9122a375d0082b8a09c53efa7e975eab4f38
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"
8 #include "base/guid.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"
35 #include "url/gurl.h"
37 namespace extensions {
39 namespace notifications = api::notifications;
41 namespace {
43 const char kMissingRequiredPropertiesForCreateNotification[] =
44 "Some of the required properties are missing: type, iconUrl, title and "
45 "message.";
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 {
75 public:
76 NotificationsApiDelegate(ChromeAsyncExtensionFunction* api_function,
77 Profile* profile,
78 const std::string& extension_id,
79 const std::string& id)
80 : api_function_(api_function),
81 profile_(profile),
82 extension_id_(extension_id),
83 id_(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,
101 args.Pass());
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,
114 args.Pass());
117 std::string id() const override { return scoped_id_; }
119 private:
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_,
128 event.Pass());
131 scoped_ptr<base::ListValue> CreateBaseEventArgs() {
132 scoped_ptr<base::ListValue> args(new base::ListValue());
133 args->Append(new base::StringValue(id_));
134 return args.Pass();
137 scoped_refptr<ChromeAsyncExtensionFunction> api_function_;
138 Profile* profile_;
139 const std::string extension_id_;
140 const std::string id_;
141 const std::string scoped_id_;
143 DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate);
146 } // namespace
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);
171 return false;
174 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
176 float image_scale =
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));
184 gfx::Image icon;
186 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
187 image_scale,
188 bitmap_sizes.icon_size,
189 options->icon_bitmap.get(),
190 &icon)) {
191 SetError(kUnableToDecodeIconError);
192 return false;
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(
199 image_scale,
200 bitmap_sizes.app_icon_mask_size,
201 options->app_icon_mask_bitmap.get(),
202 &optional_fields.small_image)) {
203 SetError(kUnableToDecodeIconError);
204 return false;
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(
223 image_scale,
224 bitmap_sizes.button_icon_size,
225 (*options->buttons)[i]->icon_bitmap.get(),
226 &info.icon);
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(
237 image_scale,
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);
244 return false;
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);
251 return false;
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);
258 return false;
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);
264 return false;
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
284 // Notification
285 Notification notification(type,
286 extension_->url(),
287 title,
288 message,
289 icon,
290 message_center::NotifierId(
291 message_center::NotifierId::APPLICATION,
292 extension_->id()),
293 base::UTF8ToUTF16(extension_->name()),
294 api_delegate->id(),
295 optional_fields,
296 api_delegate);
298 g_browser_process->notification_ui_manager()->Add(notification, GetProfile());
299 return true;
302 bool NotificationsApiFunction::UpdateNotification(
303 const std::string& id,
304 api::notifications::NotificationOptions* options,
305 Notification* notification) {
306 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
307 float image_scale =
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));
313 if (options->title)
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) {
320 gfx::Image icon;
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(
328 image_scale,
329 bitmap_sizes.app_icon_mask_size,
330 options->app_icon_mask_bitmap.get(),
331 &app_icon_mask)) {
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(
351 image_scale,
352 bitmap_sizes.button_icon_size,
353 (*options->buttons)[i]->icon_bitmap.get(),
354 &button.icon);
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));
365 gfx::Image image;
366 bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage(
367 image_scale,
368 bitmap_sizes.image_size,
369 options->image_bitmap.get(),
370 &image);
371 if (has_image) {
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);
375 return false;
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);
384 return false;
386 int progress = *options->progress;
387 // Progress value should range from 0 to 100.
388 if (progress < 0 || progress > 100) {
389 SetError(kInvalidProgressValue);
390 return false;
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);
399 return false;
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,
419 GetProfile());
420 return true;
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 {
435 return false;
438 bool NotificationsApiFunction::RunAsync() {
439 if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
440 return RunNotificationsApi();
441 } else {
442 SendResponse(false);
443 return true;
447 message_center::NotificationType
448 NotificationsApiFunction::MapApiTemplateTypeToType(
449 api::notifications::TemplateType type) {
450 switch (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;
460 default:
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;
482 } else {
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, &params_->options))
494 return false;
496 SendResponse(true);
498 return true;
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
512 // with "false".
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));
519 SendResponse(true);
520 return true;
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
528 // with false.
529 // TODO(dewittj): Add more human-readable error strings if this fails.
530 bool could_update_notification = UpdateNotification(
531 params_->notification_id, &params_->options, &notification);
532 SetResult(new base::FundamentalValue(could_update_notification));
533 if (!could_update_notification)
534 return false;
536 // No trouble, created the notification, send true to the callback and
537 // succeed.
538 SendResponse(true);
539 return true;
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));
557 SendResponse(true);
559 return true;
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());
582 SendResponse(true);
584 return true;
587 NotificationsGetPermissionLevelFunction::
588 NotificationsGetPermissionLevelFunction() {}
590 NotificationsGetPermissionLevelFunction::
591 ~NotificationsGetPermissionLevelFunction() {}
593 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
594 return true;
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)));
604 SendResponse(true);
606 return true;
609 } // namespace extensions