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_ui_manager.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/common/chrome_version_info.h" 20 #include "chrome/common/extensions/api/notifications/notification_style.h" 21 #include "content/public/browser/render_process_host.h" 22 #include "content/public/browser/render_view_host.h" 23 #include "content/public/browser/web_contents.h" 24 #include "extensions/browser/event_router.h" 25 #include "extensions/common/extension.h" 26 #include "extensions/common/features/feature.h" 27 #include "third_party/skia/include/core/SkBitmap.h" 28 #include "ui/base/layout.h" 29 #include "ui/gfx/image/image.h" 30 #include "ui/gfx/image/image_skia.h" 31 #include "ui/gfx/image/image_skia_rep.h" 32 #include "ui/message_center/message_center_style.h" 33 #include "ui/message_center/notifier_settings.h" 34 #include "url/gurl.h" 35 36 namespace extensions { 37 38 namespace notifications = api::notifications; 39 40 namespace { 41 42 const char kMissingRequiredPropertiesForCreateNotification[] = 43 "Some of the required properties are missing: type, iconUrl, title and " 44 "message."; 45 const char kUnableToDecodeIconError[] = 46 "Unable to successfully use the provided image."; 47 const char kUnexpectedProgressValueForNonProgressType[] = 48 "The progress value should not be specified for non-progress notification"; 49 const char kInvalidProgressValue[] = 50 "The progress value should range from 0 to 100"; 51 const char kExtraListItemsProvided[] = 52 "List items provided for notification type != list"; 53 const char kExtraImageProvided[] = 54 "Image resource provided for notification type != image"; 55 56 // Converts an object with width, height, and data in RGBA format into an 57 // gfx::Image (in ARGB format). 58 bool NotificationBitmapToGfxImage( 59 float max_scale, 60 const gfx::Size& target_size_dips, 61 api::notifications::NotificationBitmap* notification_bitmap, 62 gfx::Image* return_image) { 63 if (!notification_bitmap) 64 return false; 65 66 const int max_device_pixel_width = target_size_dips.width() * max_scale; 67 const int max_device_pixel_height = target_size_dips.height() * max_scale; 68 69 const int BYTES_PER_PIXEL = 4; 70 71 const int width = notification_bitmap->width; 72 const int height = notification_bitmap->height; 73 74 if (width < 0 || height < 0 || width > max_device_pixel_width || 75 height > max_device_pixel_height) 76 return false; 77 78 // Ensure we have rgba data. 79 std::string* rgba_data = notification_bitmap->data.get(); 80 if (!rgba_data) 81 return false; 82 83 const size_t rgba_data_length = rgba_data->length(); 84 const size_t rgba_area = width * height; 85 86 if (rgba_data_length != rgba_area * BYTES_PER_PIXEL) 87 return false; 88 89 // Now configure the bitmap with the sanitized dimensions. 90 SkBitmap bitmap; 91 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); 92 93 // Allocate the actual backing store. 94 if (!bitmap.allocPixels()) 95 return false; 96 97 // Ensure that our bitmap and our data now refer to the same number of pixels. 98 if (rgba_data_length != bitmap.getSafeSize()) 99 return false; 100 101 uint32_t* pixels = bitmap.getAddr32(0, 0); 102 const char* c_rgba_data = rgba_data->data(); 103 104 for (size_t t = 0; t < rgba_area; ++t) { 105 // |c_rgba_data| is RGBA, pixels is ARGB. 106 size_t rgba_index = t * BYTES_PER_PIXEL; 107 pixels[t] = SkPreMultiplyColor( 108 ((c_rgba_data[rgba_index + 3] & 0xFF) << 24) | 109 ((c_rgba_data[rgba_index + 0] & 0xFF) << 16) | 110 ((c_rgba_data[rgba_index + 1] & 0xFF) << 8) | 111 ((c_rgba_data[rgba_index + 2] & 0xFF) << 0)); 112 } 113 114 // TODO(dewittj): Handle HiDPI images with more than one scale factor 115 // representation. 116 gfx::ImageSkia skia(gfx::ImageSkiaRep(bitmap, 1.0f)); 117 *return_image = gfx::Image(skia); 118 return true; 119 } 120 121 // Given an extension id and another id, returns an id that is unique 122 // relative to other extensions. 123 std::string CreateScopedIdentifier(const std::string& extension_id, 124 const std::string& id) { 125 return extension_id + "-" + id; 126 } 127 128 // Removes the unique internal identifier to send the ID as the 129 // extension expects it. 130 std::string StripScopeFromIdentifier(const std::string& extension_id, 131 const std::string& id) { 132 size_t index_of_separator = extension_id.length() + 1; 133 DCHECK_LT(index_of_separator, id.length()); 134 135 return id.substr(index_of_separator); 136 } 137 138 class NotificationsApiDelegate : public NotificationDelegate { 139 public: 140 NotificationsApiDelegate(ChromeAsyncExtensionFunction* api_function, 141 Profile* profile, 142 const std::string& extension_id, 143 const std::string& id) 144 : api_function_(api_function), 145 profile_(profile), 146 extension_id_(extension_id), 147 id_(id), 148 scoped_id_(CreateScopedIdentifier(extension_id, id)), 149 process_id_(-1) { 150 DCHECK(api_function_.get()); 151 if (api_function_->render_view_host()) 152 process_id_ = api_function->render_view_host()->GetProcess()->GetID(); 153 } 154 155 virtual void Display() OVERRIDE { } 156 157 virtual void Error() OVERRIDE {} 158 159 virtual void Close(bool by_user) OVERRIDE { 160 EventRouter::UserGestureState gesture = 161 by_user ? EventRouter::USER_GESTURE_ENABLED 162 : EventRouter::USER_GESTURE_NOT_ENABLED; 163 scoped_ptr<base::ListValue> args(CreateBaseEventArgs()); 164 args->Append(new base::FundamentalValue(by_user)); 165 SendEvent(notifications::OnClosed::kEventName, gesture, args.Pass()); 166 } 167 168 virtual void Click() OVERRIDE { 169 scoped_ptr<base::ListValue> args(CreateBaseEventArgs()); 170 SendEvent(notifications::OnClicked::kEventName, 171 EventRouter::USER_GESTURE_ENABLED, 172 args.Pass()); 173 } 174 175 virtual bool HasClickedListener() OVERRIDE { 176 return EventRouter::Get(profile_)->HasEventListener( 177 notifications::OnClicked::kEventName); 178 } 179 180 virtual void ButtonClick(int index) OVERRIDE { 181 scoped_ptr<base::ListValue> args(CreateBaseEventArgs()); 182 args->Append(new base::FundamentalValue(index)); 183 SendEvent(notifications::OnButtonClicked::kEventName, 184 EventRouter::USER_GESTURE_ENABLED, 185 args.Pass()); 186 } 187 188 virtual std::string id() const OVERRIDE { 189 return scoped_id_; 190 } 191 192 virtual int process_id() const OVERRIDE { 193 return process_id_; 194 } 195 196 virtual content::WebContents* GetWebContents() const OVERRIDE { 197 // We're holding a reference to api_function_, so we know it'll be valid 198 // until ReleaseRVH is called, and api_function_ (as a 199 // AsyncExtensionFunction) will zero out its copy of render_view_host 200 // when the RVH goes away. 201 if (!api_function_.get()) 202 return NULL; 203 content::RenderViewHost* rvh = api_function_->render_view_host(); 204 if (!rvh) 205 return NULL; 206 return content::WebContents::FromRenderViewHost(rvh); 207 } 208 209 virtual void ReleaseRenderViewHost() OVERRIDE { 210 api_function_ = NULL; 211 } 212 213 private: 214 virtual ~NotificationsApiDelegate() {} 215 216 void SendEvent(const std::string& name, 217 EventRouter::UserGestureState user_gesture, 218 scoped_ptr<base::ListValue> args) { 219 scoped_ptr<Event> event(new Event(name, args.Pass())); 220 event->user_gesture = user_gesture; 221 EventRouter::Get(profile_)->DispatchEventToExtension(extension_id_, 222 event.Pass()); 223 } 224 225 scoped_ptr<base::ListValue> CreateBaseEventArgs() { 226 scoped_ptr<base::ListValue> args(new base::ListValue()); 227 args->Append(new base::StringValue(id_)); 228 return args.Pass(); 229 } 230 231 scoped_refptr<ChromeAsyncExtensionFunction> api_function_; 232 Profile* profile_; 233 const std::string extension_id_; 234 const std::string id_; 235 const std::string scoped_id_; 236 int process_id_; 237 238 DISALLOW_COPY_AND_ASSIGN(NotificationsApiDelegate); 239 }; 240 241 } // namespace 242 243 bool NotificationsApiFunction::IsNotificationsApiAvailable() { 244 // We need to check this explicitly rather than letting 245 // _permission_features.json enforce it, because we're sharing the 246 // chrome.notifications permissions namespace with WebKit notifications. 247 return GetExtension()->is_platform_app() || GetExtension()->is_extension(); 248 } 249 250 NotificationsApiFunction::NotificationsApiFunction() { 251 } 252 253 NotificationsApiFunction::~NotificationsApiFunction() { 254 } 255 256 bool NotificationsApiFunction::CreateNotification( 257 const std::string& id, 258 api::notifications::NotificationOptions* options) { 259 // First, make sure the required fields exist: type, title, message, icon. 260 // These fields are defined as optional in IDL such that they can be used as 261 // optional for notification updates. But for notification creations, they 262 // should be present. 263 if (options->type == api::notifications::TEMPLATE_TYPE_NONE || 264 !options->icon_url || !options->title || !options->message) { 265 SetError(kMissingRequiredPropertiesForCreateNotification); 266 return false; 267 } 268 269 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes(); 270 271 float image_scale = 272 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back()); 273 274 // Extract required fields: type, title, message, and icon. 275 message_center::NotificationType type = 276 MapApiTemplateTypeToType(options->type); 277 const base::string16 title(base::UTF8ToUTF16(*options->title)); 278 const base::string16 message(base::UTF8ToUTF16(*options->message)); 279 gfx::Image icon; 280 281 if (!NotificationBitmapToGfxImage(image_scale, 282 bitmap_sizes.icon_size, 283 options->icon_bitmap.get(), 284 &icon)) { 285 SetError(kUnableToDecodeIconError); 286 return false; 287 } 288 289 // Then, handle any optional data that's been provided. 290 message_center::RichNotificationData optional_fields; 291 if (options->priority.get()) 292 optional_fields.priority = *options->priority; 293 294 if (options->event_time.get()) 295 optional_fields.timestamp = base::Time::FromJsTime(*options->event_time); 296 297 if (options->buttons.get()) { 298 // Currently we allow up to 2 buttons. 299 size_t number_of_buttons = options->buttons->size(); 300 number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons; 301 302 for (size_t i = 0; i < number_of_buttons; i++) { 303 message_center::ButtonInfo info( 304 base::UTF8ToUTF16((*options->buttons)[i]->title)); 305 NotificationBitmapToGfxImage(image_scale, 306 bitmap_sizes.button_icon_size, 307 (*options->buttons)[i]->icon_bitmap.get(), 308 &info.icon); 309 optional_fields.buttons.push_back(info); 310 } 311 } 312 313 if (options->context_message) { 314 optional_fields.context_message = 315 base::UTF8ToUTF16(*options->context_message); 316 } 317 318 bool has_image = NotificationBitmapToGfxImage(image_scale, 319 bitmap_sizes.image_size, 320 options->image_bitmap.get(), 321 &optional_fields.image); 322 // We should have an image if and only if the type is an image type. 323 if (has_image != (type == message_center::NOTIFICATION_TYPE_IMAGE)) { 324 SetError(kExtraImageProvided); 325 return false; 326 } 327 328 // We should have list items if and only if the type is a multiple type. 329 bool has_list_items = options->items.get() && options->items->size() > 0; 330 if (has_list_items != (type == message_center::NOTIFICATION_TYPE_MULTIPLE)) { 331 SetError(kExtraListItemsProvided); 332 return false; 333 } 334 335 if (options->progress.get() != NULL) { 336 // We should have progress if and only if the type is a progress type. 337 if (type != message_center::NOTIFICATION_TYPE_PROGRESS) { 338 SetError(kUnexpectedProgressValueForNonProgressType); 339 return false; 340 } 341 optional_fields.progress = *options->progress; 342 // Progress value should range from 0 to 100. 343 if (optional_fields.progress < 0 || optional_fields.progress > 100) { 344 SetError(kInvalidProgressValue); 345 return false; 346 } 347 } 348 349 if (has_list_items) { 350 using api::notifications::NotificationItem; 351 std::vector<linked_ptr<NotificationItem> >::iterator i; 352 for (i = options->items->begin(); i != options->items->end(); ++i) { 353 message_center::NotificationItem item( 354 base::UTF8ToUTF16(i->get()->title), 355 base::UTF8ToUTF16(i->get()->message)); 356 optional_fields.items.push_back(item); 357 } 358 } 359 360 if (options->is_clickable.get()) 361 optional_fields.clickable = *options->is_clickable; 362 363 NotificationsApiDelegate* api_delegate(new NotificationsApiDelegate( 364 this, GetProfile(), extension_->id(), id)); // ownership is passed to 365 // Notification 366 Notification notification(type, 367 extension_->url(), 368 title, 369 message, 370 icon, 371 blink::WebTextDirectionDefault, 372 message_center::NotifierId( 373 message_center::NotifierId::APPLICATION, 374 extension_->id()), 375 base::UTF8ToUTF16(extension_->name()), 376 base::UTF8ToUTF16(api_delegate->id()), 377 optional_fields, 378 api_delegate); 379 380 g_browser_process->notification_ui_manager()->Add(notification, GetProfile()); 381 return true; 382 } 383 384 bool NotificationsApiFunction::UpdateNotification( 385 const std::string& id, 386 api::notifications::NotificationOptions* options, 387 Notification* notification) { 388 NotificationBitmapSizes bitmap_sizes = GetNotificationBitmapSizes(); 389 float image_scale = 390 ui::GetScaleForScaleFactor(ui::GetSupportedScaleFactors().back()); 391 392 // Update optional fields if provided. 393 if (options->type != api::notifications::TEMPLATE_TYPE_NONE) 394 notification->set_type(MapApiTemplateTypeToType(options->type)); 395 if (options->title) 396 notification->set_title(base::UTF8ToUTF16(*options->title)); 397 if (options->message) 398 notification->set_message(base::UTF8ToUTF16(*options->message)); 399 400 // TODO(dewittj): Return error if this fails. 401 if (options->icon_bitmap) { 402 gfx::Image icon; 403 NotificationBitmapToGfxImage( 404 image_scale, bitmap_sizes.icon_size, options->icon_bitmap.get(), &icon); 405 notification->set_icon(icon); 406 } 407 408 if (options->priority) 409 notification->set_priority(*options->priority); 410 411 if (options->event_time) 412 notification->set_timestamp(base::Time::FromJsTime(*options->event_time)); 413 414 if (options->buttons) { 415 // Currently we allow up to 2 buttons. 416 size_t number_of_buttons = options->buttons->size(); 417 number_of_buttons = number_of_buttons > 2 ? 2 : number_of_buttons; 418 419 std::vector<message_center::ButtonInfo> buttons; 420 for (size_t i = 0; i < number_of_buttons; i++) { 421 message_center::ButtonInfo button( 422 base::UTF8ToUTF16((*options->buttons)[i]->title)); 423 NotificationBitmapToGfxImage(image_scale, 424 bitmap_sizes.button_icon_size, 425 (*options->buttons)[i]->icon_bitmap.get(), 426 &button.icon); 427 buttons.push_back(button); 428 } 429 notification->set_buttons(buttons); 430 } 431 432 if (options->context_message) { 433 notification->set_context_message( 434 base::UTF8ToUTF16(*options->context_message)); 435 } 436 437 gfx::Image image; 438 bool has_image = NotificationBitmapToGfxImage(image_scale, 439 bitmap_sizes.image_size, 440 options->image_bitmap.get(), 441 &image); 442 if (has_image) { 443 // We should have an image if and only if the type is an image type. 444 if (notification->type() != message_center::NOTIFICATION_TYPE_IMAGE) { 445 SetError(kExtraImageProvided); 446 return false; 447 } 448 notification->set_image(image); 449 } 450 451 if (options->progress) { 452 // We should have progress if and only if the type is a progress type. 453 if (notification->type() != message_center::NOTIFICATION_TYPE_PROGRESS) { 454 SetError(kUnexpectedProgressValueForNonProgressType); 455 return false; 456 } 457 int progress = *options->progress; 458 // Progress value should range from 0 to 100. 459 if (progress < 0 || progress > 100) { 460 SetError(kInvalidProgressValue); 461 return false; 462 } 463 notification->set_progress(progress); 464 } 465 466 if (options->items.get() && options->items->size() > 0) { 467 // We should have list items if and only if the type is a multiple type. 468 if (notification->type() != message_center::NOTIFICATION_TYPE_MULTIPLE) { 469 SetError(kExtraListItemsProvided); 470 return false; 471 } 472 473 std::vector<message_center::NotificationItem> items; 474 using api::notifications::NotificationItem; 475 std::vector<linked_ptr<NotificationItem> >::iterator i; 476 for (i = options->items->begin(); i != options->items->end(); ++i) { 477 message_center::NotificationItem item( 478 base::UTF8ToUTF16(i->get()->title), 479 base::UTF8ToUTF16(i->get()->message)); 480 items.push_back(item); 481 } 482 notification->set_items(items); 483 } 484 485 // Then override if it's already set. 486 if (options->is_clickable.get()) 487 notification->set_clickable(*options->is_clickable); 488 489 g_browser_process->notification_ui_manager()->Update(*notification, 490 GetProfile()); 491 return true; 492 } 493 494 bool NotificationsApiFunction::AreExtensionNotificationsAllowed() const { 495 DesktopNotificationService* service = 496 DesktopNotificationServiceFactory::GetForProfile(GetProfile()); 497 return service->IsNotifierEnabled(message_center::NotifierId( 498 message_center::NotifierId::APPLICATION, extension_->id())); 499 } 500 501 bool NotificationsApiFunction::IsNotificationsApiEnabled() const { 502 return CanRunWhileDisabled() || AreExtensionNotificationsAllowed(); 503 } 504 505 bool NotificationsApiFunction::CanRunWhileDisabled() const { 506 return false; 507 } 508 509 bool NotificationsApiFunction::RunAsync() { 510 if (IsNotificationsApiAvailable() && IsNotificationsApiEnabled()) { 511 return RunNotificationsApi(); 512 } else { 513 SendResponse(false); 514 return true; 515 } 516 } 517 518 message_center::NotificationType 519 NotificationsApiFunction::MapApiTemplateTypeToType( 520 api::notifications::TemplateType type) { 521 switch (type) { 522 case api::notifications::TEMPLATE_TYPE_NONE: 523 case api::notifications::TEMPLATE_TYPE_BASIC: 524 return message_center::NOTIFICATION_TYPE_BASE_FORMAT; 525 case api::notifications::TEMPLATE_TYPE_IMAGE: 526 return message_center::NOTIFICATION_TYPE_IMAGE; 527 case api::notifications::TEMPLATE_TYPE_LIST: 528 return message_center::NOTIFICATION_TYPE_MULTIPLE; 529 case api::notifications::TEMPLATE_TYPE_PROGRESS: 530 return message_center::NOTIFICATION_TYPE_PROGRESS; 531 default: 532 // Gracefully handle newer application code that is running on an older 533 // runtime that doesn't recognize the requested template. 534 return message_center::NOTIFICATION_TYPE_BASE_FORMAT; 535 } 536 } 537 538 NotificationsCreateFunction::NotificationsCreateFunction() { 539 } 540 541 NotificationsCreateFunction::~NotificationsCreateFunction() { 542 } 543 544 bool NotificationsCreateFunction::RunNotificationsApi() { 545 params_ = api::notifications::Create::Params::Create(*args_); 546 EXTENSION_FUNCTION_VALIDATE(params_.get()); 547 548 const std::string extension_id(extension_->id()); 549 std::string notification_id; 550 if (!params_->notification_id.empty()) { 551 // If the caller provided a notificationId, use that. 552 notification_id = params_->notification_id; 553 } else { 554 // Otherwise, use a randomly created GUID. In case that GenerateGUID returns 555 // the empty string, simply generate a random string. 556 notification_id = base::GenerateGUID(); 557 if (notification_id.empty()) 558 notification_id = base::RandBytesAsString(16); 559 } 560 561 SetResult(new base::StringValue(notification_id)); 562 563 // TODO(dewittj): Add more human-readable error strings if this fails. 564 if (!CreateNotification(notification_id, ¶ms_->options)) 565 return false; 566 567 SendResponse(true); 568 569 return true; 570 } 571 572 NotificationsUpdateFunction::NotificationsUpdateFunction() { 573 } 574 575 NotificationsUpdateFunction::~NotificationsUpdateFunction() { 576 } 577 578 bool NotificationsUpdateFunction::RunNotificationsApi() { 579 params_ = api::notifications::Update::Params::Create(*args_); 580 EXTENSION_FUNCTION_VALIDATE(params_.get()); 581 582 // We are in update. If the ID doesn't exist, succeed but call the callback 583 // with "false". 584 const Notification* matched_notification = 585 g_browser_process->notification_ui_manager()->FindById( 586 CreateScopedIdentifier(extension_->id(), params_->notification_id)); 587 if (!matched_notification) { 588 SetResult(new base::FundamentalValue(false)); 589 SendResponse(true); 590 return true; 591 } 592 593 // Copy the existing notification to get a writable version of it. 594 Notification notification = *matched_notification; 595 596 // If we have trouble updating the notification (could be improper use of API 597 // or some other reason), mark the function as failed, calling the callback 598 // with false. 599 // TODO(dewittj): Add more human-readable error strings if this fails. 600 bool could_update_notification = UpdateNotification( 601 params_->notification_id, ¶ms_->options, ¬ification); 602 SetResult(new base::FundamentalValue(could_update_notification)); 603 if (!could_update_notification) 604 return false; 605 606 // No trouble, created the notification, send true to the callback and 607 // succeed. 608 SendResponse(true); 609 return true; 610 } 611 612 NotificationsClearFunction::NotificationsClearFunction() { 613 } 614 615 NotificationsClearFunction::~NotificationsClearFunction() { 616 } 617 618 bool NotificationsClearFunction::RunNotificationsApi() { 619 params_ = api::notifications::Clear::Params::Create(*args_); 620 EXTENSION_FUNCTION_VALIDATE(params_.get()); 621 622 bool cancel_result = g_browser_process->notification_ui_manager()->CancelById( 623 CreateScopedIdentifier(extension_->id(), params_->notification_id)); 624 625 SetResult(new base::FundamentalValue(cancel_result)); 626 SendResponse(true); 627 628 return true; 629 } 630 631 NotificationsGetAllFunction::NotificationsGetAllFunction() {} 632 633 NotificationsGetAllFunction::~NotificationsGetAllFunction() {} 634 635 bool NotificationsGetAllFunction::RunNotificationsApi() { 636 NotificationUIManager* notification_ui_manager = 637 g_browser_process->notification_ui_manager(); 638 std::set<std::string> notification_ids = 639 notification_ui_manager->GetAllIdsByProfileAndSourceOrigin( 640 GetProfile(), extension_->url()); 641 642 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); 643 644 for (std::set<std::string>::iterator iter = notification_ids.begin(); 645 iter != notification_ids.end(); iter++) { 646 result->SetBooleanWithoutPathExpansion( 647 StripScopeFromIdentifier(extension_->id(), *iter), true); 648 } 649 650 SetResult(result.release()); 651 SendResponse(true); 652 653 return true; 654 } 655 656 NotificationsGetPermissionLevelFunction:: 657 NotificationsGetPermissionLevelFunction() {} 658 659 NotificationsGetPermissionLevelFunction:: 660 ~NotificationsGetPermissionLevelFunction() {} 661 662 bool NotificationsGetPermissionLevelFunction::CanRunWhileDisabled() const { 663 return true; 664 } 665 666 bool NotificationsGetPermissionLevelFunction::RunNotificationsApi() { 667 api::notifications::PermissionLevel result = 668 AreExtensionNotificationsAllowed() 669 ? api::notifications::PERMISSION_LEVEL_GRANTED 670 : api::notifications::PERMISSION_LEVEL_DENIED; 671 672 SetResult(new base::StringValue(api::notifications::ToString(result))); 673 SendResponse(true); 674 675 return true; 676 } 677 678 } // namespace extensions 679