Upstream version 10.39.225.0
[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   virtual void Display() OVERRIDE { }
89
90   virtual void Error() OVERRIDE {}
91
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());
99   }
100
101   virtual void Click() OVERRIDE {
102     scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
103     SendEvent(notifications::OnClicked::kEventName,
104               EventRouter::USER_GESTURE_ENABLED,
105               args.Pass());
106   }
107
108   virtual bool HasClickedListener() OVERRIDE {
109     return EventRouter::Get(profile_)->HasEventListener(
110         notifications::OnClicked::kEventName);
111   }
112
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,
118               args.Pass());
119   }
120
121   virtual std::string id() const OVERRIDE {
122     return scoped_id_;
123   }
124
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())
131       return NULL;
132     content::RenderViewHost* rvh = api_function_->render_view_host();
133     if (!rvh)
134       return NULL;
135     return content::WebContents::FromRenderViewHost(rvh);
136   }
137
138   virtual void ReleaseRenderViewHost() OVERRIDE {
139     api_function_ = NULL;
140   }
141
142  private:
143   virtual ~NotificationsApiDelegate() {}
144
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_,
151                                                          event.Pass());
152   }
153
154   scoped_ptr<base::ListValue> CreateBaseEventArgs() {
155     scoped_ptr<base::ListValue> args(new base::ListValue());
156     args->Append(new base::StringValue(id_));
157     return args.Pass();
158   }
159
160   scoped_refptr<ChromeAsyncExtensionFunction> api_function_;
161   Profile* profile_;
162   const std::string extension_id_;
163   const std::string id_;
164   const std::string scoped_id_;
165
166   DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate);
167 };
168
169 }  // namespace
170
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();
176 }
177
178 NotificationsApiFunction::NotificationsApiFunction() {
179 }
180
181 NotificationsApiFunction::~NotificationsApiFunction() {
182 }
183
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);
194     return false;
195   }
196
197   NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
198
199   float image_scale =
200       ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
201
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));
207   gfx::Image icon;
208
209   if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
210           image_scale,
211           bitmap_sizes.icon_size,
212           options->icon_bitmap.get(),
213           &icon)) {
214     SetError(kUnableToDecodeIconError);
215     return false;
216   }
217
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(
222             image_scale,
223             bitmap_sizes.app_icon_mask_size,
224             options->app_icon_mask_bitmap.get(),
225             &optional_fields.small_image)) {
226       SetError(kUnableToDecodeIconError);
227       return false;
228     }
229   }
230
231   if (options->priority.get())
232     optional_fields.priority = *options->priority;
233
234   if (options->event_time.get())
235     optional_fields.timestamp = base::Time::FromJsTime(*options->event_time);
236
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;
241
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(
246           image_scale,
247           bitmap_sizes.button_icon_size,
248           (*options->buttons)[i]->icon_bitmap.get(),
249           &info.icon);
250       optional_fields.buttons.push_back(info);
251     }
252   }
253
254   if (options->context_message) {
255     optional_fields.context_message =
256         base::UTF8ToUTF16(*options->context_message);
257   }
258
259   bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage(
260       image_scale,
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);
267     return false;
268   }
269
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);
274     return false;
275   }
276
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);
281       return false;
282     }
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);
287       return false;
288     }
289   }
290
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);
299     }
300   }
301
302   if (options->is_clickable.get())
303     optional_fields.clickable = *options->is_clickable;
304
305   NotificationsApiDelegate* api_delegate(new NotificationsApiDelegate(
306       this, GetProfile(), extension_->id(), id));  // ownership is passed to
307                                                    // Notification
308   Notification notification(type,
309                             extension_->url(),
310                             title,
311                             message,
312                             icon,
313                             blink::WebTextDirectionDefault,
314                             message_center::NotifierId(
315                                 message_center::NotifierId::APPLICATION,
316                                 extension_->id()),
317                             base::UTF8ToUTF16(extension_->name()),
318                             base::UTF8ToUTF16(api_delegate->id()),
319                             optional_fields,
320                             api_delegate);
321
322   g_browser_process->notification_ui_manager()->Add(notification, GetProfile());
323   return true;
324 }
325
326 bool NotificationsApiFunction::UpdateNotification(
327     const std::string& id,
328     api::notifications::NotificationOptions* options,
329     Notification* notification) {
330   NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes();
331   float image_scale =
332       ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
333
334   // Update optional fields if provided.
335   if (options->type != api::notifications::TEMPLATE_TYPE_NONE)
336     notification->set_type(MapApiTemplateTypeToType(options->type));
337   if (options->title)
338     notification->set_title(base::UTF8ToUTF16(*options->title));
339   if (options->message)
340     notification->set_message(base::UTF8ToUTF16(*options->message));
341
342   // TODO(dewittj): Return error if this fails.
343   if (options->icon_bitmap) {
344     gfx::Image icon;
345     NotificationConversionHelper::NotificationBitmapToGfxImage(
346         image_scale, bitmap_sizes.icon_size, options->icon_bitmap.get(), &icon);
347     notification->set_icon(icon);
348   }
349
350   gfx::Image app_icon_mask;
351   if (NotificationConversionHelper::NotificationBitmapToGfxImage(
352           image_scale,
353           bitmap_sizes.app_icon_mask_size,
354           options->app_icon_mask_bitmap.get(),
355           &app_icon_mask)) {
356     notification->set_small_image(app_icon_mask);
357   }
358
359   if (options->priority)
360     notification->set_priority(*options->priority);
361
362   if (options->event_time)
363     notification->set_timestamp(base::Time::FromJsTime(*options->event_time));
364
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;
369
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(
375           image_scale,
376           bitmap_sizes.button_icon_size,
377           (*options->buttons)[i]->icon_bitmap.get(),
378           &button.icon);
379       buttons.push_back(button);
380     }
381     notification->set_buttons(buttons);
382   }
383
384   if (options->context_message) {
385     notification->set_context_message(
386         base::UTF8ToUTF16(*options->context_message));
387   }
388
389   gfx::Image image;
390   bool has_image = NotificationConversionHelper::NotificationBitmapToGfxImage(
391       image_scale,
392       bitmap_sizes.image_size,
393       options->image_bitmap.get(),
394       &image);
395   if (has_image) {
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);
399       return false;
400     }
401     notification->set_image(image);
402   }
403
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);
408       return false;
409     }
410     int progress = *options->progress;
411     // Progress value should range from 0 to 100.
412     if (progress < 0 || progress > 100) {
413       SetError(kInvalidProgressValue);
414       return false;
415     }
416     notification->set_progress(progress);
417   }
418
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);
423       return false;
424     }
425
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);
434     }
435     notification->set_items(items);
436   }
437
438   // Then override if it's already set.
439   if (options->is_clickable.get())
440     notification->set_clickable(*options->is_clickable);
441
442   g_browser_process->notification_ui_manager()->Update(*notification,
443                                                        GetProfile());
444   return true;
445 }
446
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()));
452 }
453
454 bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
455   return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
456 }
457
458 bool NotificationsApiFunction::CanRunWhileDisabled() const {
459   return false;
460 }
461
462 bool NotificationsApiFunction::RunAsync() {
463   if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
464     return RunNotificationsApi();
465   } else {
466     SendResponse(false);
467     return true;
468   }
469 }
470
471 message_center::NotificationType
472 NotificationsApiFunction::MapApiTemplateTypeToType(
473     api::notifications::TemplateType type) {
474   switch (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;
484     default:
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;
488   }
489 }
490
491 NotificationsCreateFunction::NotificationsCreateFunction() {
492 }
493
494 NotificationsCreateFunction::~NotificationsCreateFunction() {
495 }
496
497 bool NotificationsCreateFunction::RunNotificationsApi() {
498   params_ = api::notifications::Create::Params::Create(*args_);
499   EXTENSION_FUNCTION_VALIDATE(params_.get());
500
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;
506   } else {
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);
512   }
513
514   SetResult(new base::StringValue(notification_id));
515
516   // TODO(dewittj): Add more human-readable error strings if this fails.
517   if (!CreateNotification(notification_id, &params_->options))
518     return false;
519
520   SendResponse(true);
521
522   return true;
523 }
524
525 NotificationsUpdateFunction::NotificationsUpdateFunction() {
526 }
527
528 NotificationsUpdateFunction::~NotificationsUpdateFunction() {
529 }
530
531 bool NotificationsUpdateFunction::RunNotificationsApi() {
532   params_ = api::notifications::Update::Params::Create(*args_);
533   EXTENSION_FUNCTION_VALIDATE(params_.get());
534
535   // We are in update.  If the ID doesn't exist, succeed but call the callback
536   // with "false".
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));
542     SendResponse(true);
543     return true;
544   }
545
546   // Copy the existing notification to get a writable version of it.
547   Notification notification = *matched_notification;
548
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
551   // with false.
552   // TODO(dewittj): Add more human-readable error strings if this fails.
553   bool could_update_notification = UpdateNotification(
554       params_->notification_id, &params_->options, &notification);
555   SetResult(new base::FundamentalValue(could_update_notification));
556   if (!could_update_notification)
557     return false;
558
559   // No trouble, created the notification, send true to the callback and
560   // succeed.
561   SendResponse(true);
562   return true;
563 }
564
565 NotificationsClearFunction::NotificationsClearFunction() {
566 }
567
568 NotificationsClearFunction::~NotificationsClearFunction() {
569 }
570
571 bool NotificationsClearFunction::RunNotificationsApi() {
572   params_ = api::notifications::Clear::Params::Create(*args_);
573   EXTENSION_FUNCTION_VALIDATE(params_.get());
574
575   bool cancel_result = g_browser_process->notification_ui_manager()->CancelById(
576       CreateScopedIdentifier(extension_->id(), params_->notification_id));
577
578   SetResult(new base::FundamentalValue(cancel_result));
579   SendResponse(true);
580
581   return true;
582 }
583
584 NotificationsGetAllFunction::NotificationsGetAllFunction() {}
585
586 NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
587
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());
594
595   scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
596
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);
601   }
602
603   SetResult(result.release());
604   SendResponse(true);
605
606   return true;
607 }
608
609 NotificationsGetPermissionLevelFunction::
610 NotificationsGetPermissionLevelFunction() {}
611
612 NotificationsGetPermissionLevelFunction::
613 ~NotificationsGetPermissionLevelFunction() {}
614
615 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
616   return true;
617 }
618
619 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() {
620   api::notifications::PermissionLevel result =
621       AreExtensionNotificationsAllowed()
622           ? api::notifications::PERMISSION_LEVEL_GRANTED
623           : api::notifications::PERMISSION_LEVEL_DENIED;
624
625   SetResult(new base::StringValue(api::notifications::ToString(result)));
626   SendResponse(true);
627
628   return true;
629 }
630
631 }  // namespace extensions