3830a7cf3358656bc3cd5745470456688d1350d0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / notifications / notifications_api.cc
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.
4
5 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
6
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"
36
37 namespace extensions {
38
39 namespace notifications = api::notifications;
40
41 namespace {
42
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";
56
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;
62 }
63
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());
70
71   return id.substr(index_of_separator);
72 }
73
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());
86   }
87
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());
95   }
96
97   void Click() override {
98     scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
99     SendEvent(notifications::OnClicked::kEventName,
100               EventRouter::USER_GESTURE_ENABLED,
101               args.Pass());
102   }
103
104   bool HasClickedListener() override {
105     return EventRouter::Get(profile_)->HasEventListener(
106         notifications::OnClicked::kEventName);
107   }
108
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());
115   }
116
117   std::string id() const override { return scoped_id_; }
118
119  private:
120   ~NotificationsApiDelegate() override {}
121
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());
129   }
130
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();
135   }
136
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_;
142
143   DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate);
144 };
145
146 }  // namespace
147
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();
153 }
154
155 NotificationsApiFunction::NotificationsApiFunction() {
156 }
157
158 NotificationsApiFunction::~NotificationsApiFunction() {
159 }
160
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;
172   }
173
174   NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
175
176   float image_scale =
177       ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
178
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;
185
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;
193   }
194
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;
205     }
206   }
207
208   if (options->priority.get())
209     optional_fields.priority = *options->priority;
210
211   if (options->event_time.get())
212     optional_fields.timestamp = base::Time::FromJsTime(*options->event_time);
213
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;
218
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);
228     }
229   }
230
231   if (options->context_message) {
232     optional_fields.context_message =
233         base::UTF8ToUTF16(*options->context_message);
234   }
235
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;
245   }
246
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;
252   }
253
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;
259     }
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;
265     }
266   }
267
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);
276     }
277   }
278
279   if (options->is_clickable.get())
280     optional_fields.clickable = *options->is_clickable;
281
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                             blink::WebTextDirectionDefault,
291                             message_center::NotifierId(
292                                 message_center::NotifierId::APPLICATION,
293                                 extension_->id()),
294                             base::UTF8ToUTF16(extension_->name()),
295                             base::UTF8ToUTF16(api_delegate->id()),
296                             optional_fields,
297                             api_delegate);
298
299   g_browser_process->notification_ui_manager()->Add(notification, GetProfile());
300   return true;
301 }
302
303 bool NotificationsApiFunction::UpdateNotification(
304     const std::string& id,
305     api::notifications::NotificationOptions* options,
306     Notification* notification) {
307   NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
308   float image_scale =
309       ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
310
311   // Update optional fields if provided.
312   if (options->type != api::notifications::TEMPLATE_TYPE_NONE)
313     notification->set_type(MapApiTemplateTypeToType(options->type));
314   if (options->title)
315     notification->set_title(base::UTF8ToUTF16(*options->title));
316   if (options->message)
317     notification->set_message(base::UTF8ToUTF16(*options->message));
318
319   // TODO(dewittj): Return error if this fails.
320   if (options->icon_bitmap) {
321     gfx::Image icon;
322     NotificationConversionHelper::NotificationBitmapToGfxImage(
323         image_scale, bitmap_sizes.icon_size, options->icon_bitmap.get(), &icon);
324     notification->set_icon(icon);
325   }
326
327   gfx::Image app_icon_mask;
328   if (NotificationConversionHelper::NotificationBitmapToGfxImage(
329           image_scale,
330           bitmap_sizes.app_icon_mask_size,
331           options->app_icon_mask_bitmap.get(),
332           &app_icon_mask)) {
333     notification->set_small_image(app_icon_mask);
334   }
335
336   if (options->priority)
337     notification->set_priority(*options->priority);
338
339   if (options->event_time)
340     notification->set_timestamp(base::Time::FromJsTime(*options->event_time));
341
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;
346
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(
352           image_scale,
353           bitmap_sizes.button_icon_size,
354           (*options->buttons)[i]->icon_bitmap.get(),
355           &button.icon);
356       buttons.push_back(button);
357     }
358     notification->set_buttons(buttons);
359   }
360
361   if (options->context_message) {
362     notification->set_context_message(
363         base::UTF8ToUTF16(*options->context_message));
364   }
365
366   gfx::Image image;
367   bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage(
368       image_scale,
369       bitmap_sizes.image_size,
370       options->image_bitmap.get(),
371       &image);
372   if (has_image) {
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);
376       return false;
377     }
378     notification->set_image(image);
379   }
380
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);
385       return false;
386     }
387     int progress = *options->progress;
388     // Progress value should range from 0 to 100.
389     if (progress < 0 || progress > 100) {
390       SetError(kInvalidProgressValue);
391       return false;
392     }
393     notification->set_progress(progress);
394   }
395
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);
400       return false;
401     }
402
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);
411     }
412     notification->set_items(items);
413   }
414
415   // Then override if it's already set.
416   if (options->is_clickable.get())
417     notification->set_clickable(*options->is_clickable);
418
419   g_browser_process->notification_ui_manager()->Update(*notification,
420                                                        GetProfile());
421   return true;
422 }
423
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()));
429 }
430
431 bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
432   return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
433 }
434
435 bool NotificationsApiFunction::CanRunWhileDisabled() const {
436   return false;
437 }
438
439 bool NotificationsApiFunction::RunAsync() {
440   if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
441     return RunNotificationsApi();
442   } else {
443     SendResponse(false);
444     return true;
445   }
446 }
447
448 message_center::NotificationType
449 NotificationsApiFunction::MapApiTemplateTypeToType(
450     api::notifications::TemplateType type) {
451   switch (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;
461     default:
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;
465   }
466 }
467
468 NotificationsCreateFunction::NotificationsCreateFunction() {
469 }
470
471 NotificationsCreateFunction::~NotificationsCreateFunction() {
472 }
473
474 bool NotificationsCreateFunction::RunNotificationsApi() {
475   params_ = api::notifications::Create::Params::Create(*args_);
476   EXTENSION_FUNCTION_VALIDATE(params_.get());
477
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;
483   } else {
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);
489   }
490
491   SetResult(new base::StringValue(notification_id));
492
493   // TODO(dewittj): Add more human-readable error strings if this fails.
494   if (!CreateNotification(notification_id, &params_->options))
495     return false;
496
497   SendResponse(true);
498
499   return true;
500 }
501
502 NotificationsUpdateFunction::NotificationsUpdateFunction() {
503 }
504
505 NotificationsUpdateFunction::~NotificationsUpdateFunction() {
506 }
507
508 bool NotificationsUpdateFunction::RunNotificationsApi() {
509   params_ = api::notifications::Update::Params::Create(*args_);
510   EXTENSION_FUNCTION_VALIDATE(params_.get());
511
512   // We are in update.  If the ID doesn't exist, succeed but call the callback
513   // with "false".
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));
520     SendResponse(true);
521     return true;
522   }
523
524   // Copy the existing notification to get a writable version of it.
525   Notification notification = *matched_notification;
526
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
529   // with false.
530   // TODO(dewittj): Add more human-readable error strings if this fails.
531   bool could_update_notification = UpdateNotification(
532       params_->notification_id, &params_->options, &notification);
533   SetResult(new base::FundamentalValue(could_update_notification));
534   if (!could_update_notification)
535     return false;
536
537   // No trouble, created the notification, send true to the callback and
538   // succeed.
539   SendResponse(true);
540   return true;
541 }
542
543 NotificationsClearFunction::NotificationsClearFunction() {
544 }
545
546 NotificationsClearFunction::~NotificationsClearFunction() {
547 }
548
549 bool NotificationsClearFunction::RunNotificationsApi() {
550   params_ = api::notifications::Clear::Params::Create(*args_);
551   EXTENSION_FUNCTION_VALIDATE(params_.get());
552
553   bool cancel_result = g_browser_process->notification_ui_manager()->CancelById(
554       CreateScopedIdentifier(extension_->id(), params_->notification_id),
555       NotificationUIManager::GetProfileID(GetProfile()));
556
557   SetResult(new base::FundamentalValue(cancel_result));
558   SendResponse(true);
559
560   return true;
561 }
562
563 NotificationsGetAllFunction::NotificationsGetAllFunction() {}
564
565 NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
566
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());
573
574   scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
575
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);
580   }
581
582   SetResult(result.release());
583   SendResponse(true);
584
585   return true;
586 }
587
588 NotificationsGetPermissionLevelFunction::
589 NotificationsGetPermissionLevelFunction() {}
590
591 NotificationsGetPermissionLevelFunction::
592 ~NotificationsGetPermissionLevelFunction() {}
593
594 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
595   return true;
596 }
597
598 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() {
599   api::notifications::PermissionLevel result =
600       AreExtensionNotificationsAllowed()
601           ? api::notifications::PERMISSION_LEVEL_GRANTED
602           : api::notifications::PERMISSION_LEVEL_DENIED;
603
604   SetResult(new base::StringValue(api::notifications::ToString(result)));
605   SendResponse(true);
606
607   return true;
608 }
609
610 }  // namespace extensions