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.
5 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
7 #include "base/callback.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/notification.h"
15 #include "chrome/browser/notifications/notification_conversion_helper.h"
16 #include "chrome/browser/notifications/notification_ui_manager.h"
17 #include "chrome/browser/notifications/notifier_state_tracker.h"
18 #include "chrome/browser/notifications/notifier_state_tracker_factory.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/common/extensions/api/notifications/notification_style.h"
21 #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
22 #include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
23 #include "content/public/browser/render_process_host.h"
24 #include "content/public/browser/render_view_host.h"
25 #include "content/public/browser/web_contents.h"
26 #include "extensions/browser/event_router.h"
27 #include "extensions/browser/extension_system_provider.h"
28 #include "extensions/browser/extensions_browser_client.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/features/feature.h"
31 #include "third_party/skia/include/core/SkBitmap.h"
32 #include "ui/base/layout.h"
33 #include "ui/gfx/image/image.h"
34 #include "ui/gfx/image/image_skia.h"
35 #include "ui/gfx/image/image_skia_operations.h"
36 #include "ui/gfx/image/image_skia_rep.h"
37 #include "ui/message_center/message_center_style.h"
38 #include "ui/message_center/notifier_settings.h"
41 namespace extensions
{
43 namespace notifications
= api::notifications
;
47 const char kMissingRequiredPropertiesForCreateNotification
[] =
48 "Some of the required properties are missing: type, iconUrl, title and "
50 const char kUnableToDecodeIconError
[] =
51 "Unable to successfully use the provided image.";
52 const char kUnexpectedProgressValueForNonProgressType
[] =
53 "The progress value should not be specified for non-progress notification";
54 const char kInvalidProgressValue
[] =
55 "The progress value should range from 0 to 100";
56 const char kExtraListItemsProvided
[] =
57 "List items provided for notification type != list";
58 const char kExtraImageProvided
[] =
59 "Image resource provided for notification type != image";
61 // Given an extension id and another id, returns an id that is unique
62 // relative to other extensions.
63 std::string
CreateScopedIdentifier(const std::string
& extension_id
,
64 const std::string
& id
) {
65 return extension_id
+ "-" + id
;
68 // Removes the unique internal identifier to send the ID as the
69 // extension expects it.
70 std::string
StripScopeFromIdentifier(const std::string
& extension_id
,
71 const std::string
& id
) {
72 size_t index_of_separator
= extension_id
.length() + 1;
73 DCHECK_LT(index_of_separator
, id
.length());
75 return id
.substr(index_of_separator
);
78 const gfx::ImageSkia
CreateSolidColorImage(int width
,
82 bitmap
.allocN32Pixels(width
, height
);
83 bitmap
.eraseColor(color
);
84 return gfx::ImageSkia::CreateFrom1xBitmap(bitmap
);
87 // Take the alpha channel of small_image, mask it with the foreground,
88 // then add the masked foreground on top of the background
89 const gfx::Image
GetMaskedSmallImage(const gfx::ImageSkia
& small_image
) {
90 int width
= small_image
.width();
91 int height
= small_image
.height();
93 // Background color grey
94 const gfx::ImageSkia background
= CreateSolidColorImage(
95 width
, height
, message_center::kSmallImageMaskBackgroundColor
);
96 // Foreground color white
97 const gfx::ImageSkia foreground
= CreateSolidColorImage(
98 width
, height
, message_center::kSmallImageMaskForegroundColor
);
99 const gfx::ImageSkia masked_small_image
=
100 gfx::ImageSkiaOperations::CreateMaskedImage(foreground
, small_image
);
101 return gfx::Image(gfx::ImageSkiaOperations::CreateSuperimposedImage(
102 background
, masked_small_image
));
105 class ShutdownNotifierFactory
106 : public BrowserContextKeyedServiceShutdownNotifierFactory
{
108 static ShutdownNotifierFactory
* GetInstance() {
109 return base::Singleton
<ShutdownNotifierFactory
>::get();
113 friend struct base::DefaultSingletonTraits
<ShutdownNotifierFactory
>;
115 ShutdownNotifierFactory()
116 : BrowserContextKeyedServiceShutdownNotifierFactory(
117 "NotificationsApiDelegate") {
118 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
120 ~ShutdownNotifierFactory() override
{}
122 DISALLOW_COPY_AND_ASSIGN(ShutdownNotifierFactory
);
125 class NotificationsApiDelegate
: public NotificationDelegate
{
127 NotificationsApiDelegate(ChromeAsyncExtensionFunction
* api_function
,
129 const std::string
& extension_id
,
130 const std::string
& id
)
131 : api_function_(api_function
),
132 event_router_(EventRouter::Get(profile
)),
133 extension_id_(extension_id
),
135 scoped_id_(CreateScopedIdentifier(extension_id
, id
)) {
136 DCHECK(api_function_
);
137 shutdown_notifier_subscription_
=
138 ShutdownNotifierFactory::GetInstance()->Get(profile
)->Subscribe(
139 base::Bind(&NotificationsApiDelegate::Shutdown
,
140 base::Unretained(this)));
143 void Close(bool by_user
) override
{
144 EventRouter::UserGestureState gesture
=
145 by_user
? EventRouter::USER_GESTURE_ENABLED
146 : EventRouter::USER_GESTURE_NOT_ENABLED
;
147 scoped_ptr
<base::ListValue
> args(CreateBaseEventArgs());
148 args
->Append(new base::FundamentalValue(by_user
));
149 SendEvent(events::NOTIFICATIONS_ON_CLOSED
,
150 notifications::OnClosed::kEventName
, gesture
, args
.Pass());
153 void Click() override
{
154 scoped_ptr
<base::ListValue
> args(CreateBaseEventArgs());
155 SendEvent(events::NOTIFICATIONS_ON_CLICKED
,
156 notifications::OnClicked::kEventName
,
157 EventRouter::USER_GESTURE_ENABLED
, args
.Pass());
160 bool HasClickedListener() override
{
164 return event_router_
->HasEventListener(
165 notifications::OnClicked::kEventName
);
168 void ButtonClick(int index
) override
{
169 scoped_ptr
<base::ListValue
> args(CreateBaseEventArgs());
170 args
->Append(new base::FundamentalValue(index
));
171 SendEvent(events::NOTIFICATIONS_ON_BUTTON_CLICKED
,
172 notifications::OnButtonClicked::kEventName
,
173 EventRouter::USER_GESTURE_ENABLED
, args
.Pass());
176 std::string
id() const override
{ return scoped_id_
; }
179 ~NotificationsApiDelegate() override
{}
181 void SendEvent(events::HistogramValue histogram_value
,
182 const std::string
& name
,
183 EventRouter::UserGestureState user_gesture
,
184 scoped_ptr
<base::ListValue
> args
) {
188 scoped_ptr
<Event
> event(new Event(histogram_value
, name
, args
.Pass()));
189 event
->user_gesture
= user_gesture
;
190 event_router_
->DispatchEventToExtension(extension_id_
, event
.Pass());
194 event_router_
= nullptr;
195 shutdown_notifier_subscription_
.reset();
198 scoped_ptr
<base::ListValue
> CreateBaseEventArgs() {
199 scoped_ptr
<base::ListValue
> args(new base::ListValue());
200 args
->Append(new base::StringValue(id_
));
204 scoped_refptr
<ChromeAsyncExtensionFunction
> api_function_
;
206 // Since this class is refcounted it may outlive the profile. We listen for
207 // profile-keyed service shutdown events and reset to nullptr at that time,
208 // so make sure to check for a valid pointer before use.
209 EventRouter
* event_router_
;
211 const std::string extension_id_
;
212 const std::string id_
;
213 const std::string scoped_id_
;
215 scoped_ptr
<KeyedServiceShutdownNotifier::Subscription
>
216 shutdown_notifier_subscription_
;
218 DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate
);
223 bool NotificationsApiFunction::IsNotificationsApiAvailable() {
224 // We need to check this explicitly rather than letting
225 // _permission_features.json enforce it, because we're sharing the
226 // chrome.notifications permissions namespace with WebKit notifications.
227 return extension()->is_platform_app() || extension()->is_extension();
230 NotificationsApiFunction::NotificationsApiFunction() {
233 NotificationsApiFunction::~NotificationsApiFunction() {
236 bool NotificationsApiFunction::CreateNotification(
237 const std::string
& id
,
238 api::notifications::NotificationOptions
* options
) {
239 // First, make sure the required fields exist: type, title, message, icon.
240 // These fields are defined as optional in IDL such that they can be used as
241 // optional for notification updates. But for notification creations, they
242 // should be present.
243 if (options
->type
== api::notifications::TEMPLATE_TYPE_NONE
||
244 !options
->icon_url
|| !options
->title
|| !options
->message
) {
245 SetError(kMissingRequiredPropertiesForCreateNotification
);
249 NotificationBitmapSizes bitmap_sizes
= GetNotificationBitmapSizes();
252 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
254 // Extract required fields: type, title, message, and icon.
255 message_center::NotificationType type
=
256 MapApiTemplateTypeToType(options
->type
);
257 const base::string16
title(base::UTF8ToUTF16(*options
->title
));
258 const base::string16
message(base::UTF8ToUTF16(*options
->message
));
261 if (!options
->icon_bitmap
.get() ||
262 !NotificationConversionHelper::NotificationBitmapToGfxImage(
263 image_scale
, bitmap_sizes
.icon_size
, *options
->icon_bitmap
, &icon
)) {
264 SetError(kUnableToDecodeIconError
);
268 // Then, handle any optional data that's been provided.
269 message_center::RichNotificationData optional_fields
;
270 if (options
->app_icon_mask_url
.get()) {
271 gfx::Image small_icon_mask
;
272 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
273 image_scale
, bitmap_sizes
.app_icon_mask_size
,
274 *options
->app_icon_mask_bitmap
, &small_icon_mask
)) {
275 SetError(kUnableToDecodeIconError
);
278 optional_fields
.small_image
=
279 GetMaskedSmallImage(small_icon_mask
.AsImageSkia());
282 if (options
->priority
.get())
283 optional_fields
.priority
= *options
->priority
;
285 if (options
->event_time
.get())
286 optional_fields
.timestamp
= base::Time::FromJsTime(*options
->event_time
);
288 if (options
->buttons
.get()) {
289 // Currently we allow up to 2 buttons.
290 size_t number_of_buttons
= options
->buttons
->size();
291 number_of_buttons
= number_of_buttons
> 2 ? 2 : number_of_buttons
;
293 for (size_t i
= 0; i
< number_of_buttons
; i
++) {
294 message_center::ButtonInfo
info(
295 base::UTF8ToUTF16((*options
->buttons
)[i
]->title
));
296 extensions::api::notifications::NotificationBitmap
* icon_bitmap_ptr
=
297 (*options
->buttons
)[i
]->icon_bitmap
.get();
298 if (icon_bitmap_ptr
) {
299 NotificationConversionHelper::NotificationBitmapToGfxImage(
300 image_scale
, bitmap_sizes
.button_icon_size
, *icon_bitmap_ptr
,
303 optional_fields
.buttons
.push_back(info
);
307 if (options
->context_message
) {
308 optional_fields
.context_message
=
309 base::UTF8ToUTF16(*options
->context_message
);
312 bool has_image
= options
->image_bitmap
.get() &&
313 NotificationConversionHelper::NotificationBitmapToGfxImage(
314 image_scale
, bitmap_sizes
.image_size
,
315 *options
->image_bitmap
, &optional_fields
.image
);
317 // We should have an image if and only if the type is an image type.
318 if (has_image
!= (type
== message_center::NOTIFICATION_TYPE_IMAGE
)) {
319 SetError(kExtraImageProvided
);
323 // We should have list items if and only if the type is a multiple type.
324 bool has_list_items
= options
->items
.get() && options
->items
->size() > 0;
325 if (has_list_items
!= (type
== message_center::NOTIFICATION_TYPE_MULTIPLE
)) {
326 SetError(kExtraListItemsProvided
);
330 if (options
->progress
.get() != NULL
) {
331 // We should have progress if and only if the type is a progress type.
332 if (type
!= message_center::NOTIFICATION_TYPE_PROGRESS
) {
333 SetError(kUnexpectedProgressValueForNonProgressType
);
336 optional_fields
.progress
= *options
->progress
;
337 // Progress value should range from 0 to 100.
338 if (optional_fields
.progress
< 0 || optional_fields
.progress
> 100) {
339 SetError(kInvalidProgressValue
);
344 if (has_list_items
) {
345 using api::notifications::NotificationItem
;
346 std::vector
<linked_ptr
<NotificationItem
> >::iterator i
;
347 for (i
= options
->items
->begin(); i
!= options
->items
->end(); ++i
) {
348 message_center::NotificationItem
item(
349 base::UTF8ToUTF16(i
->get()->title
),
350 base::UTF8ToUTF16(i
->get()->message
));
351 optional_fields
.items
.push_back(item
);
355 if (options
->is_clickable
.get())
356 optional_fields
.clickable
= *options
->is_clickable
;
358 NotificationsApiDelegate
* api_delegate(new NotificationsApiDelegate(
359 this, GetProfile(), extension_
->id(), id
)); // ownership is passed to
361 Notification
notification(
362 type
, title
, message
, icon
,
363 message_center::NotifierId(message_center::NotifierId::APPLICATION
,
365 base::UTF8ToUTF16(extension_
->name()), extension_
->url(),
366 api_delegate
->id(), optional_fields
, api_delegate
);
368 g_browser_process
->notification_ui_manager()->Add(notification
, GetProfile());
372 bool NotificationsApiFunction::UpdateNotification(
373 const std::string
& id
,
374 api::notifications::NotificationOptions
* options
,
375 Notification
* notification
) {
376 NotificationBitmapSizes bitmap_sizes
= GetNotificationBitmapSizes();
378 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back());
380 // Update optional fields if provided.
381 if (options
->type
!= api::notifications::TEMPLATE_TYPE_NONE
)
382 notification
->set_type(MapApiTemplateTypeToType(options
->type
));
384 notification
->set_title(base::UTF8ToUTF16(*options
->title
));
385 if (options
->message
)
386 notification
->set_message(base::UTF8ToUTF16(*options
->message
));
388 if (options
->icon_bitmap
.get()) {
390 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
391 image_scale
, bitmap_sizes
.icon_size
, *options
->icon_bitmap
,
393 SetError(kUnableToDecodeIconError
);
396 notification
->set_icon(icon
);
399 if (options
->app_icon_mask_bitmap
.get()) {
400 gfx::Image app_icon_mask
;
401 if (!NotificationConversionHelper::NotificationBitmapToGfxImage(
402 image_scale
, bitmap_sizes
.app_icon_mask_size
,
403 *options
->app_icon_mask_bitmap
, &app_icon_mask
)) {
404 SetError(kUnableToDecodeIconError
);
407 notification
->set_small_image(
408 GetMaskedSmallImage(app_icon_mask
.AsImageSkia()));
411 if (options
->priority
)
412 notification
->set_priority(*options
->priority
);
414 if (options
->event_time
)
415 notification
->set_timestamp(base::Time::FromJsTime(*options
->event_time
));
417 if (options
->buttons
) {
418 // Currently we allow up to 2 buttons.
419 size_t number_of_buttons
= options
->buttons
->size();
420 number_of_buttons
= number_of_buttons
> 2 ? 2 : number_of_buttons
;
422 std::vector
<message_center::ButtonInfo
> buttons
;
423 for (size_t i
= 0; i
< number_of_buttons
; i
++) {
424 message_center::ButtonInfo
button(
425 base::UTF8ToUTF16((*options
->buttons
)[i
]->title
));
426 extensions::api::notifications::NotificationBitmap
* icon_bitmap_ptr
=
427 (*options
->buttons
)[i
]->icon_bitmap
.get();
428 if (icon_bitmap_ptr
) {
429 NotificationConversionHelper::NotificationBitmapToGfxImage(
430 image_scale
, bitmap_sizes
.button_icon_size
, *icon_bitmap_ptr
,
433 buttons
.push_back(button
);
435 notification
->set_buttons(buttons
);
438 if (options
->context_message
) {
439 notification
->set_context_message(
440 base::UTF8ToUTF16(*options
->context_message
));
445 options
->image_bitmap
.get() &&
446 NotificationConversionHelper::NotificationBitmapToGfxImage(
447 image_scale
, bitmap_sizes
.image_size
, *options
->image_bitmap
, &image
);
450 // We should have an image if and only if the type is an image type.
451 if (notification
->type() != message_center::NOTIFICATION_TYPE_IMAGE
) {
452 SetError(kExtraImageProvided
);
455 notification
->set_image(image
);
458 if (options
->progress
) {
459 // We should have progress if and only if the type is a progress type.
460 if (notification
->type() != message_center::NOTIFICATION_TYPE_PROGRESS
) {
461 SetError(kUnexpectedProgressValueForNonProgressType
);
464 int progress
= *options
->progress
;
465 // Progress value should range from 0 to 100.
466 if (progress
< 0 || progress
> 100) {
467 SetError(kInvalidProgressValue
);
470 notification
->set_progress(progress
);
473 if (options
->items
.get() && options
->items
->size() > 0) {
474 // We should have list items if and only if the type is a multiple type.
475 if (notification
->type() != message_center::NOTIFICATION_TYPE_MULTIPLE
) {
476 SetError(kExtraListItemsProvided
);
480 std::vector
<message_center::NotificationItem
> items
;
481 using api::notifications::NotificationItem
;
482 std::vector
<linked_ptr
<NotificationItem
> >::iterator i
;
483 for (i
= options
->items
->begin(); i
!= options
->items
->end(); ++i
) {
484 message_center::NotificationItem
item(
485 base::UTF8ToUTF16(i
->get()->title
),
486 base::UTF8ToUTF16(i
->get()->message
));
487 items
.push_back(item
);
489 notification
->set_items(items
);
492 // Then override if it's already set.
493 if (options
->is_clickable
.get())
494 notification
->set_clickable(*options
->is_clickable
);
496 g_browser_process
->notification_ui_manager()->Update(*notification
,
501 bool NotificationsApiFunction::AreExtensionNotificationsAllowed() const {
502 NotifierStateTracker
* notifier_state_tracker
=
503 NotifierStateTrackerFactory::GetForProfile(GetProfile());
505 return notifier_state_tracker
->IsNotifierEnabled(
506 message_center::NotifierId(message_center::NotifierId::APPLICATION
,
510 bool NotificationsApiFunction::IsNotificationsApiEnabled() const {
511 return CanRunWhileDisabled() || AreExtensionNotificationsAllowed();
514 bool NotificationsApiFunction::CanRunWhileDisabled() const {
518 bool NotificationsApiFunction::RunAsync() {
519 if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) {
520 return RunNotificationsApi();
527 message_center::NotificationType
528 NotificationsApiFunction::MapApiTemplateTypeToType(
529 api::notifications::TemplateType type
) {
531 case api::notifications::TEMPLATE_TYPE_NONE
:
532 case api::notifications::TEMPLATE_TYPE_BASIC
:
533 return message_center::NOTIFICATION_TYPE_BASE_FORMAT
;
534 case api::notifications::TEMPLATE_TYPE_IMAGE
:
535 return message_center::NOTIFICATION_TYPE_IMAGE
;
536 case api::notifications::TEMPLATE_TYPE_LIST
:
537 return message_center::NOTIFICATION_TYPE_MULTIPLE
;
538 case api::notifications::TEMPLATE_TYPE_PROGRESS
:
539 return message_center::NOTIFICATION_TYPE_PROGRESS
;
541 // Gracefully handle newer application code that is running on an older
542 // runtime that doesn't recognize the requested template.
543 return message_center::NOTIFICATION_TYPE_BASE_FORMAT
;
547 NotificationsCreateFunction::NotificationsCreateFunction() {
550 NotificationsCreateFunction::~NotificationsCreateFunction() {
553 bool NotificationsCreateFunction::RunNotificationsApi() {
554 params_
= api::notifications::Create::Params::Create(*args_
);
555 EXTENSION_FUNCTION_VALIDATE(params_
.get());
557 const std::string
extension_id(extension_
->id());
558 std::string notification_id
;
559 if (params_
->notification_id
.get() && !params_
->notification_id
->empty()) {
560 // If the caller provided a notificationId, use that.
561 notification_id
= *params_
->notification_id
;
563 // Otherwise, use a randomly created GUID. In case that GenerateGUID returns
564 // the empty string, simply generate a random string.
565 notification_id
= base::GenerateGUID();
566 if (notification_id
.empty())
567 notification_id
= base::RandBytesAsString(16);
570 SetResult(new base::StringValue(notification_id
));
572 // TODO(dewittj): Add more human-readable error strings if this fails.
573 if (!CreateNotification(notification_id
, ¶ms_
->options
))
581 NotificationsUpdateFunction::NotificationsUpdateFunction() {
584 NotificationsUpdateFunction::~NotificationsUpdateFunction() {
587 bool NotificationsUpdateFunction::RunNotificationsApi() {
588 params_
= api::notifications::Update::Params::Create(*args_
);
589 EXTENSION_FUNCTION_VALIDATE(params_
.get());
591 // We are in update. If the ID doesn't exist, succeed but call the callback
593 const Notification
* matched_notification
=
594 g_browser_process
->notification_ui_manager()->FindById(
595 CreateScopedIdentifier(extension_
->id(), params_
->notification_id
),
596 NotificationUIManager::GetProfileID(GetProfile()));
597 if (!matched_notification
) {
598 SetResult(new base::FundamentalValue(false));
603 // Copy the existing notification to get a writable version of it.
604 Notification notification
= *matched_notification
;
606 // If we have trouble updating the notification (could be improper use of API
607 // or some other reason), mark the function as failed, calling the callback
609 // TODO(dewittj): Add more human-readable error strings if this fails.
610 bool could_update_notification
= UpdateNotification(
611 params_
->notification_id
, ¶ms_
->options
, ¬ification
);
612 SetResult(new base::FundamentalValue(could_update_notification
));
613 if (!could_update_notification
)
616 // No trouble, created the notification, send true to the callback and
622 NotificationsClearFunction::NotificationsClearFunction() {
625 NotificationsClearFunction::~NotificationsClearFunction() {
628 bool NotificationsClearFunction::RunNotificationsApi() {
629 params_
= api::notifications::Clear::Params::Create(*args_
);
630 EXTENSION_FUNCTION_VALIDATE(params_
.get());
632 bool cancel_result
= g_browser_process
->notification_ui_manager()->CancelById(
633 CreateScopedIdentifier(extension_
->id(), params_
->notification_id
),
634 NotificationUIManager::GetProfileID(GetProfile()));
636 SetResult(new base::FundamentalValue(cancel_result
));
642 NotificationsGetAllFunction::NotificationsGetAllFunction() {}
644 NotificationsGetAllFunction::~NotificationsGetAllFunction() {}
646 bool NotificationsGetAllFunction::RunNotificationsApi() {
647 NotificationUIManager
* notification_ui_manager
=
648 g_browser_process
->notification_ui_manager();
649 std::set
<std::string
> notification_ids
=
650 notification_ui_manager
->GetAllIdsByProfileAndSourceOrigin(
651 NotificationUIManager::GetProfileID(GetProfile()), extension_
->url());
653 scoped_ptr
<base::DictionaryValue
> result(new base::DictionaryValue());
655 for (std::set
<std::string
>::iterator iter
= notification_ids
.begin();
656 iter
!= notification_ids
.end(); iter
++) {
657 result
->SetBooleanWithoutPathExpansion(
658 StripScopeFromIdentifier(extension_
->id(), *iter
), true);
661 SetResult(result
.release());
667 NotificationsGetPermissionLevelFunction::
668 NotificationsGetPermissionLevelFunction() {}
670 NotificationsGetPermissionLevelFunction::
671 ~NotificationsGetPermissionLevelFunction() {}
673 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const {
677 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() {
678 api::notifications::PermissionLevel result
=
679 AreExtensionNotificationsAllowed()
680 ? api::notifications::PERMISSION_LEVEL_GRANTED
681 : api::notifications::PERMISSION_LEVEL_DENIED
;
683 SetResult(new base::StringValue(api::notifications::ToString(result
)));
689 } // namespace extensions