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
37namespace extensions {
38
39namespace notifications = api::notifications;
40
41namespace {
42
43const char kMissingRequiredPropertiesForCreateNotification[] =
44    "Some of the required properties are missing: type, iconUrl, title and "
45    "message.";
46const char kUnableToDecodeIconError[] =
47    "Unable to successfully use the provided image.";
48const char kUnexpectedProgressValueForNonProgressType[] =
49    "The progress value should not be specified for non-progress notification";
50const char kInvalidProgressValue[] =
51    "The progress value should range from 0 to 100";
52const char kExtraListItemsProvided[] =
53    "List items provided for notification type != list";
54const 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.
59std::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.
66std::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
74class 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
171bool 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
178NotificationsApiFunction::NotificationsApiFunction() {
179}
180
181NotificationsApiFunction::~NotificationsApiFunction() {
182}
183
184bool 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
326bool 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
447bool 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
454bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
455  return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
456}
457
458bool NotificationsApiFunction::CanRunWhileDisabled() const {
459  return false;
460}
461
462bool NotificationsApiFunction::RunAsync() {
463  if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
464    return RunNotificationsApi();
465  } else {
466    SendResponse(false);
467    return true;
468  }
469}
470
471message_center::NotificationType
472NotificationsApiFunction::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
491NotificationsCreateFunction::NotificationsCreateFunction() {
492}
493
494NotificationsCreateFunction::~NotificationsCreateFunction() {
495}
496
497bool 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
525NotificationsUpdateFunction::NotificationsUpdateFunction() {
526}
527
528NotificationsUpdateFunction::~NotificationsUpdateFunction() {
529}
530
531bool 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
565NotificationsClearFunction::NotificationsClearFunction() {
566}
567
568NotificationsClearFunction::~NotificationsClearFunction() {
569}
570
571bool 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
584NotificationsGetAllFunction::NotificationsGetAllFunction() {}
585
586NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
587
588bool 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
609NotificationsGetPermissionLevelFunction::
610NotificationsGetPermissionLevelFunction() {}
611
612NotificationsGetPermissionLevelFunction::
613~NotificationsGetPermissionLevelFunction() {}
614
615bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
616  return true;
617}
618
619bool 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
632