Home | History | Annotate | Download | only in notifications
      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/strings/string_number_conversions.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "base/time/time.h"
     11 #include "chrome/browser/browser_process.h"
     12 #include "chrome/browser/extensions/event_names.h"
     13 #include "chrome/browser/extensions/event_router.h"
     14 #include "chrome/browser/extensions/extension_system.h"
     15 #include "chrome/browser/notifications/desktop_notification_service.h"
     16 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
     17 #include "chrome/browser/notifications/notification.h"
     18 #include "chrome/browser/notifications/notification_ui_manager.h"
     19 #include "chrome/common/chrome_version_info.h"
     20 #include "chrome/common/extensions/extension.h"
     21 #include "chrome/common/extensions/features/feature.h"
     22 #include "content/public/browser/render_process_host.h"
     23 #include "content/public/browser/render_view_host.h"
     24 #include "third_party/skia/include/core/SkBitmap.h"
     25 #include "ui/gfx/image/image.h"
     26 #include "ui/gfx/image/image_skia.h"
     27 #include "ui/gfx/image/image_skia_rep.h"
     28 #include "ui/message_center/message_center_style.h"
     29 #include "ui/message_center/message_center_util.h"
     30 #include "ui/message_center/notifier_settings.h"
     31 #include "url/gurl.h"
     32 
     33 namespace extensions {
     34 
     35 namespace {
     36 
     37 const char kResultKey[] = "result";
     38 const char kMissingRequiredPropertiesForCreateNotification[] =
     39     "Some of the required properties are missing: type, iconUrl, title and "
     40     "message.";
     41 const char kUnexpectedProgressValueForNonProgressType[] =
     42     "The progress value should not be specified for non-progress notification";
     43 const char kInvalidProgressValue[] =
     44     "The progress value should range from 0 to 100";
     45 
     46 // Converts an object with width, height, and data in RGBA format into an
     47 // gfx::Image (in ARGB format).
     48 bool NotificationBitmapToGfxImage(
     49     api::notifications::NotificationBitmap* notification_bitmap,
     50     gfx::Image* return_image) {
     51   if (!notification_bitmap)
     52     return false;
     53 
     54   // Ensure a sane set of dimensions.
     55   const int max_width = message_center::kNotificationPreferredImageSize;
     56   const int max_height =
     57       message_center::kNotificationPreferredImageRatio * max_width;
     58   const int BYTES_PER_PIXEL = 4;
     59 
     60   const int width = notification_bitmap->width;
     61   const int height = notification_bitmap->height;
     62 
     63   if (width < 0 || height < 0 || width > max_width || height > max_height)
     64     return false;
     65 
     66   // Ensure we have rgba data.
     67   std::string* rgba_data = notification_bitmap->data.get();
     68   if (!rgba_data)
     69     return false;
     70 
     71   const size_t rgba_data_length = rgba_data->length();
     72   const size_t rgba_area = width * height;
     73 
     74   if (rgba_data_length != rgba_area * BYTES_PER_PIXEL)
     75     return false;
     76 
     77   // Now configure the bitmap with the sanitized dimensions.
     78   SkBitmap bitmap;
     79   bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
     80 
     81   // Allocate the actual backing store.
     82   if (!bitmap.allocPixels())
     83     return false;
     84 
     85   // Ensure that our bitmap and our data now refer to the same number of pixels.
     86   if (rgba_data_length != bitmap.getSafeSize())
     87     return false;
     88 
     89   uint32_t* pixels = bitmap.getAddr32(0, 0);
     90   const char* c_rgba_data = rgba_data->data();
     91 
     92   for (size_t t = 0; t < rgba_area; ++t) {
     93     // |c_rgba_data| is RGBA, pixels is ARGB.
     94     size_t rgba_index = t * BYTES_PER_PIXEL;
     95     pixels[t] = SkPreMultiplyColor(
     96         ((c_rgba_data[rgba_index + 3] & 0xFF) << 24) |
     97         ((c_rgba_data[rgba_index + 0] & 0xFF) << 16) |
     98         ((c_rgba_data[rgba_index + 1] & 0xFF) << 8) |
     99         ((c_rgba_data[rgba_index + 2] & 0xFF) << 0));
    100   }
    101 
    102   // TODO(dewittj): Handle HiDPI images.
    103   ui::ScaleFactor scale_factor(ui::SCALE_FACTOR_100P);
    104   gfx::ImageSkia skia(gfx::ImageSkiaRep(bitmap, scale_factor));
    105   *return_image = gfx::Image(skia);
    106   return true;
    107 }
    108 
    109 // Given an extension id and another id, returns an id that is unique
    110 // relative to other extensions.
    111 std::string CreateScopedIdentifier(const std::string& extension_id,
    112                                    const std::string& id) {
    113   return extension_id + "-" + id;
    114 }
    115 
    116 // Removes the unique internal identifier to send the ID as the
    117 // extension expects it.
    118 std::string StripScopeFromIdentifier(const std::string& extension_id,
    119                                      const std::string& id) {
    120   size_t index_of_separator = extension_id.length() + 1;
    121   DCHECK_LT(index_of_separator, id.length());
    122 
    123   return id.substr(index_of_separator);
    124 }
    125 
    126 class NotificationsApiDelegate : public NotificationDelegate {
    127  public:
    128   NotificationsApiDelegate(ApiFunction* api_function,
    129                            Profile* profile,
    130                            const std::string& extension_id,
    131                            const std::string& id)
    132       : api_function_(api_function),
    133         profile_(profile),
    134         extension_id_(extension_id),
    135         id_(id),
    136         scoped_id_(CreateScopedIdentifier(extension_id, id)),
    137         process_id_(-1) {
    138     DCHECK(api_function_.get());
    139     if (api_function_->render_view_host())
    140       process_id_ = api_function->render_view_host()->GetProcess()->GetID();
    141   }
    142 
    143   virtual void Display() OVERRIDE { }
    144 
    145   virtual void Error() OVERRIDE {
    146     scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
    147     SendEvent(event_names::kOnNotificationError, args.Pass());
    148   }
    149 
    150   virtual void Close(bool by_user) OVERRIDE {
    151     scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
    152     args->Append(Value::CreateBooleanValue(by_user));
    153     SendEvent(event_names::kOnNotificationClosed, args.Pass());
    154   }
    155 
    156   virtual void Click() OVERRIDE {
    157     scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
    158     SendEvent(event_names::kOnNotificationClicked, args.Pass());
    159   }
    160 
    161   virtual bool HasClickedListener() OVERRIDE {
    162     return ExtensionSystem::Get(profile_)->event_router()->HasEventListener(
    163         event_names::kOnNotificationClicked);
    164   }
    165 
    166   virtual void ButtonClick(int index) OVERRIDE {
    167     scoped_ptr<base::ListValue> args(CreateBaseEventArgs());
    168     args->Append(Value::CreateIntegerValue(index));
    169     SendEvent(event_names::kOnNotificationButtonClicked, args.Pass());
    170   }
    171 
    172   virtual std::string id() const OVERRIDE {
    173     return scoped_id_;
    174   }
    175 
    176   virtual int process_id() const OVERRIDE {
    177     return process_id_;
    178   }
    179 
    180   virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
    181     // We're holding a reference to api_function_, so we know it'll be valid
    182     // until ReleaseRVH is called, and api_function_ (as a
    183     // UIThreadExtensionFunction) will zero out its copy of render_view_host
    184     // when the RVH goes away.
    185     if (!api_function_.get())
    186       return NULL;
    187     return api_function_->render_view_host();
    188   }
    189 
    190   virtual void ReleaseRenderViewHost() OVERRIDE {
    191     api_function_ = NULL;
    192   }
    193 
    194  private:
    195   virtual ~NotificationsApiDelegate() {}
    196 
    197   void SendEvent(const std::string& name, scoped_ptr<base::ListValue> args) {
    198     scoped_ptr<Event> event(new Event(name, args.Pass()));
    199     ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension(
    200         extension_id_, event.Pass());
    201   }
    202 
    203   scoped_ptr<base::ListValue> CreateBaseEventArgs() {
    204     scoped_ptr<base::ListValue> args(new base::ListValue());
    205     args->Append(Value::CreateStringValue(id_));
    206     return args.Pass();
    207   }
    208 
    209   scoped_refptr<ApiFunction> api_function_;
    210   Profile* profile_;
    211   const std::string extension_id_;
    212   const std::string id_;
    213   const std::string scoped_id_;
    214   int process_id_;
    215 
    216   DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate);
    217 };
    218 
    219 }  // namespace
    220 
    221 bool NotificationsApiFunction::IsNotificationsApiAvailable() {
    222   // We need to check this explicitly rather than letting
    223   // _permission_features.json enforce it, because we're sharing the
    224   // chrome.notifications permissions namespace with WebKit notifications.
    225   return GetExtension()->is_platform_app() || GetExtension()->is_extension();
    226 }
    227 
    228 NotificationsApiFunction::NotificationsApiFunction() {
    229 }
    230 
    231 NotificationsApiFunction::~NotificationsApiFunction() {
    232 }
    233 
    234 bool NotificationsApiFunction::CreateNotification(
    235     const std::string& id,
    236     api::notifications::NotificationOptions* options) {
    237   // First, make sure the required fields exist: type, title, message,  icon.
    238   // These fields are defined as optional in IDL such that they can be used as
    239   // optional for notification updates. But for notification creations, they
    240   // should be present.
    241   if (options->type == api::notifications::TEMPLATE_TYPE_NONE ||
    242       !options->icon_url || !options->title || !options->message) {
    243     SetError(kMissingRequiredPropertiesForCreateNotification);
    244     return false;
    245   }
    246 
    247   // Extract required fields: type, title, message, and icon.
    248   message_center::NotificationType type =
    249       MapApiTemplateTypeToType(options->type);
    250   const string16 title(UTF8ToUTF16(*options->title));
    251   const string16 message(UTF8ToUTF16(*options->message));
    252   gfx::Image icon;
    253 
    254   // TODO(dewittj): Return error if this fails.
    255   NotificationBitmapToGfxImage(options->icon_bitmap.get(), &icon);
    256 
    257   // Then, handle any optional data that's been provided.
    258   message_center::RichNotificationData optional_fields;
    259   if (message_center::IsRichNotificationEnabled()) {
    260     if (options->priority.get())
    261       optional_fields.priority = *options->priority;
    262 
    263     if (options->event_time.get())
    264       optional_fields.timestamp = base::Time::FromJsTime(*options->event_time);
    265 
    266     if (options->buttons.get()) {
    267       // Currently we allow up to 2 buttons.
    268       size_t number_of_buttons = options->buttons->size();
    269       number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons;
    270 
    271       for (size_t i = 0; i < number_of_buttons; i++) {
    272         message_center::ButtonInfo info(
    273             UTF8ToUTF16((*options->buttons)[i]->title));
    274         NotificationBitmapToGfxImage((*options->buttons)[i]->icon_bitmap.get(),
    275                                      &info.icon);
    276         optional_fields.buttons.push_back(info);
    277       }
    278     }
    279 
    280     if (options->expanded_message.get()) {
    281       optional_fields.expanded_message =
    282           UTF8ToUTF16(*options->expanded_message);
    283     }
    284 
    285     bool has_image = NotificationBitmapToGfxImage(options->image_bitmap.get(),
    286                                                   &optional_fields.image);
    287     // We should have an image if and only if the type is an image type.
    288     if (has_image != (type == message_center::NOTIFICATION_TYPE_IMAGE))
    289       return false;
    290 
    291     // We should have list items if and only if the type is a multiple type.
    292     bool has_list_items = options->items.get() && options->items->size() > 0;
    293     if (has_list_items != (type == message_center::NOTIFICATION_TYPE_MULTIPLE))
    294       return false;
    295 
    296     if (options->progress.get() != NULL) {
    297       // We should have progress if and only if the type is a progress type.
    298       if (type != message_center::NOTIFICATION_TYPE_PROGRESS) {
    299         SetError(kUnexpectedProgressValueForNonProgressType);
    300         return false;
    301       }
    302       optional_fields.progress = *options->progress;
    303       // Progress value should range from 0 to 100.
    304       if (optional_fields.progress < 0 || optional_fields.progress > 100) {
    305         SetError(kInvalidProgressValue);
    306         return false;
    307       }
    308     }
    309 
    310     if (has_list_items) {
    311       using api::notifications::NotificationItem;
    312       std::vector<linked_ptr<NotificationItem> >::iterator i;
    313       for (i = options->items->begin(); i != options->items->end(); ++i) {
    314         message_center::NotificationItem item(UTF8ToUTF16(i->get()->title),
    315                                               UTF8ToUTF16(i->get()->message));
    316         optional_fields.items.push_back(item);
    317       }
    318     }
    319   }
    320 
    321   NotificationsApiDelegate* api_delegate(new NotificationsApiDelegate(
    322       this,
    323       profile(),
    324       extension_->id(),
    325       id));  // ownership is passed to Notification
    326   Notification notification(type,
    327                             extension_->url(),
    328                             title,
    329                             message,
    330                             icon,
    331                             WebKit::WebTextDirectionDefault,
    332                             UTF8ToUTF16(extension_->name()),
    333                             UTF8ToUTF16(api_delegate->id()),
    334                             optional_fields,
    335                             api_delegate);
    336 
    337   g_browser_process->notification_ui_manager()->Add(notification, profile());
    338   return true;
    339 }
    340 
    341 bool NotificationsApiFunction::UpdateNotification(
    342     const std::string& id,
    343     api::notifications::NotificationOptions* options,
    344     Notification* notification) {
    345   // Update optional fields if provided.
    346   if (options->type != api::notifications::TEMPLATE_TYPE_NONE)
    347     notification->set_type(MapApiTemplateTypeToType(options->type));
    348   if (options->title)
    349     notification->set_title(UTF8ToUTF16(*options->title));
    350   if (options->message)
    351     notification->set_message(UTF8ToUTF16(*options->message));
    352 
    353   // TODO(dewittj): Return error if this fails.
    354   if (options->icon_bitmap) {
    355     gfx::Image icon;
    356     NotificationBitmapToGfxImage(options->icon_bitmap.get(), &icon);
    357     notification->set_icon(icon);
    358   }
    359 
    360   message_center::RichNotificationData optional_fields;
    361   if (message_center::IsRichNotificationEnabled()) {
    362     if (options->priority)
    363       notification->set_priority(*options->priority);
    364 
    365     if (options->event_time)
    366       notification->set_timestamp(base::Time::FromJsTime(*options->event_time));
    367 
    368     if (options->buttons) {
    369       // Currently we allow up to 2 buttons.
    370       size_t number_of_buttons = options->buttons->size();
    371       number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons;
    372 
    373       for (size_t i = 0; i < number_of_buttons; i++) {
    374         message_center::ButtonInfo info(
    375             UTF8ToUTF16((*options->buttons)[i]->title));
    376         NotificationBitmapToGfxImage((*options->buttons)[i]->icon_bitmap.get(),
    377                                      &info.icon);
    378         optional_fields.buttons.push_back(info);
    379       }
    380     }
    381 
    382     if (options->expanded_message) {
    383       notification->set_expanded_message(
    384           UTF8ToUTF16(*options->expanded_message));
    385     }
    386 
    387     gfx::Image image;
    388     if (NotificationBitmapToGfxImage(options->image_bitmap.get(), &image)) {
    389       // We should have an image if and only if the type is an image type.
    390       if (notification->type() != message_center::NOTIFICATION_TYPE_IMAGE)
    391         return false;
    392       notification->set_image(image);
    393     }
    394 
    395     if (options->progress) {
    396       // We should have progress if and only if the type is a progress type.
    397       if (notification->type() != message_center::NOTIFICATION_TYPE_PROGRESS) {
    398         SetError(kUnexpectedProgressValueForNonProgressType);
    399         return false;
    400       }
    401       int progress = *options->progress;
    402       // Progress value should range from 0 to 100.
    403       if (progress < 0 || progress > 100) {
    404         SetError(kInvalidProgressValue);
    405         return false;
    406       }
    407       notification->set_progress(progress);
    408     }
    409 
    410     if (options->items.get() && options->items->size() > 0) {
    411       // We should have list items if and only if the type is a multiple type.
    412       if (notification->type() != message_center::NOTIFICATION_TYPE_MULTIPLE)
    413         return false;
    414 
    415       std::vector< message_center::NotificationItem> items;
    416       using api::notifications::NotificationItem;
    417       std::vector<linked_ptr<NotificationItem> >::iterator i;
    418       for (i = options->items->begin(); i != options->items->end(); ++i) {
    419         message_center::NotificationItem item(UTF8ToUTF16(i->get()->title),
    420                                               UTF8ToUTF16(i->get()->message));
    421         items.push_back(item);
    422       }
    423       notification->set_items(items);
    424     }
    425   }
    426 
    427   g_browser_process->notification_ui_manager()->Update(
    428       *notification, profile());
    429   return true;
    430 }
    431 
    432 bool NotificationsApiFunction::IsNotificationsApiEnabled() {
    433   DesktopNotificationService* service =
    434       DesktopNotificationServiceFactory::GetForProfile(profile());
    435   return service->IsNotifierEnabled(message_center::NotifierId(
    436       message_center::NotifierId::APPLICATION, extension_->id()));
    437 }
    438 
    439 bool NotificationsApiFunction::RunImpl() {
    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 const char kNotificationPrefix[] = "extensions.api.";
    469 
    470 static uint64 next_id_ = 0;
    471 
    472 NotificationsCreateFunction::NotificationsCreateFunction() {
    473 }
    474 
    475 NotificationsCreateFunction::~NotificationsCreateFunction() {
    476 }
    477 
    478 bool NotificationsCreateFunction::RunNotificationsApi() {
    479   params_ = api::notifications::Create::Params::Create(*args_);
    480   EXTENSION_FUNCTION_VALIDATE(params_.get());
    481 
    482   // If the caller provided a notificationId, use that. Otherwise, generate
    483   // one. Note that there's nothing stopping an app developer from passing in
    484   // arbitrary "extension.api.999" notificationIds that will collide with
    485   // future generated IDs. It doesn't seem necessary to try to prevent this; if
    486   // developers want to hurt themselves, we'll let them.
    487   const std::string extension_id(extension_->id());
    488   std::string notification_id;
    489   if (!params_->notification_id.empty())
    490     notification_id = params_->notification_id;
    491   else
    492     notification_id = kNotificationPrefix + base::Uint64ToString(next_id_++);
    493 
    494   SetResult(Value::CreateStringValue(notification_id));
    495 
    496   // TODO(dewittj): Add more human-readable error strings if this fails.
    497   if (!CreateNotification(notification_id, &params_->options))
    498     return false;
    499 
    500   SendResponse(true);
    501 
    502   return true;
    503 }
    504 
    505 NotificationsUpdateFunction::NotificationsUpdateFunction() {
    506 }
    507 
    508 NotificationsUpdateFunction::~NotificationsUpdateFunction() {
    509 }
    510 
    511 bool NotificationsUpdateFunction::RunNotificationsApi() {
    512   params_ = api::notifications::Update::Params::Create(*args_);
    513   EXTENSION_FUNCTION_VALIDATE(params_.get());
    514 
    515   // We are in update.  If the ID doesn't exist, succeed but call the callback
    516   // with "false".
    517   const Notification* matched_notification =
    518       g_browser_process->notification_ui_manager()->FindById(
    519           CreateScopedIdentifier(extension_->id(), params_->notification_id));
    520   if (!matched_notification) {
    521     SetResult(Value::CreateBooleanValue(false));
    522     SendResponse(true);
    523     return true;
    524   }
    525 
    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   Notification notification = *matched_notification;
    531   bool could_update_notification = UpdateNotification(
    532       params_->notification_id, &params_->options, &notification);
    533   SetResult(Value::CreateBooleanValue(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 
    556   SetResult(Value::CreateBooleanValue(cancel_result));
    557   SendResponse(true);
    558 
    559   return true;
    560 }
    561 
    562 NotificationsGetAllFunction::NotificationsGetAllFunction() {}
    563 
    564 NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
    565 
    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           profile_, extension_->url());
    572 
    573   scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue());
    574 
    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);
    579   }
    580 
    581   SetResult(result.release());
    582   SendResponse(true);
    583 
    584   return true;
    585 }
    586 
    587 }  // namespace extensions
    588