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/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
    632