message_center_notification_manager.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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/notifications/message_center_notification_manager.h" 6 7#include "base/logging.h" 8#include "base/memory/scoped_ptr.h" 9#include "base/prefs/pref_registry_simple.h" 10#include "base/prefs/pref_service.h" 11#include "base/stl_util.h" 12#include "chrome/browser/chrome_notification_types.h" 13#include "chrome/browser/extensions/api/notification_provider/notification_provider_api.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/fullscreen_notification_blocker.h" 17#include "chrome/browser/notifications/message_center_settings_controller.h" 18#include "chrome/browser/notifications/notification.h" 19#include "chrome/browser/notifications/notification_conversion_helper.h" 20#include "chrome/browser/notifications/screen_lock_notification_blocker.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/ui/browser_finder.h" 23#include "chrome/browser/ui/chrome_pages.h" 24#include "chrome/browser/ui/host_desktop.h" 25#include "chrome/common/extensions/api/notification_provider.h" 26#include "chrome/common/pref_names.h" 27#include "content/public/browser/notification_service.h" 28#include "content/public/browser/web_contents.h" 29#include "content/public/common/url_constants.h" 30#include "extensions/browser/extension_registry.h" 31#include "extensions/browser/extension_system.h" 32#include "extensions/browser/info_map.h" 33#include "extensions/common/extension_set.h" 34#include "extensions/common/permissions/permissions_data.h" 35#include "ui/gfx/image/image_skia.h" 36#include "ui/message_center/message_center_style.h" 37#include "ui/message_center/message_center_tray.h" 38#include "ui/message_center/message_center_types.h" 39#include "ui/message_center/notifier_settings.h" 40 41#if defined(OS_CHROMEOS) 42#include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h" 43#include "chrome/browser/ui/ash/multi_user/multi_user_util.h" 44#endif 45 46#if defined(USE_ASH) 47#include "ash/shell.h" 48#include "ash/system/web_notification/web_notification_tray.h" 49#endif 50 51#if defined(OS_WIN) 52// The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all 53// popups go away and the user has notifications in the message center. 54const int kFirstRunIdleDelaySeconds = 1; 55#endif 56 57MessageCenterNotificationManager::MessageCenterNotificationManager( 58 message_center::MessageCenter* message_center, 59 PrefService* local_state, 60 scoped_ptr<message_center::NotifierSettingsProvider> settings_provider) 61 : message_center_(message_center), 62#if defined(OS_WIN) 63 first_run_idle_timeout_( 64 base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)), 65 weak_factory_(this), 66#endif 67 settings_provider_(settings_provider.Pass()), 68 system_observer_(this), 69 stats_collector_(message_center), 70 google_now_stats_collector_(message_center) { 71#if defined(OS_WIN) 72 first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state); 73#endif 74 75 message_center_->AddObserver(this); 76 message_center_->SetNotifierSettingsProvider(settings_provider_.get()); 77 78#if defined(OS_CHROMEOS) 79 blockers_.push_back( 80 new LoginStateNotificationBlockerChromeOS(message_center)); 81#else 82 blockers_.push_back(new ScreenLockNotificationBlocker(message_center)); 83#endif 84 blockers_.push_back(new FullscreenNotificationBlocker(message_center)); 85 86#if defined(OS_WIN) || defined(OS_MACOSX) \ 87 || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) 88 // On Windows, Linux and Mac, the notification manager owns the tray icon and 89 // views.Other platforms have global ownership and Create will return NULL. 90 tray_.reset(message_center::CreateMessageCenterTray()); 91#endif 92 registrar_.Add(this, 93 chrome::NOTIFICATION_FULLSCREEN_CHANGED, 94 content::NotificationService::AllSources()); 95} 96 97MessageCenterNotificationManager::~MessageCenterNotificationManager() { 98 message_center_->SetNotifierSettingsProvider(NULL); 99 message_center_->RemoveObserver(this); 100 101 STLDeleteContainerPairSecondPointers(profile_notifications_.begin(), 102 profile_notifications_.end()); 103 profile_notifications_.clear(); 104} 105 106void MessageCenterNotificationManager::RegisterPrefs( 107 PrefRegistrySimple* registry) { 108 registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon, 109 false); 110 registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true); 111 registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false); 112} 113 114//////////////////////////////////////////////////////////////////////////////// 115// NotificationUIManager 116 117void MessageCenterNotificationManager::Add(const Notification& notification, 118 Profile* profile) { 119 if (Update(notification, profile)) 120 return; 121 122 DesktopNotificationServiceFactory::GetForProfile(profile)-> 123 ShowWelcomeNotificationIfNecessary(notification); 124 125 // WARNING: You MUST update the message center via the notification within a 126 // ProfileNotification object or the profile ID will not be correctly set for 127 // ChromeOS. 128 ProfileNotification* profile_notification( 129 new ProfileNotification(profile, notification, message_center_)); 130 AddProfileNotification(profile_notification); 131 132 // TODO(liyanhou): Change the logic to only send notifications to one party. 133 // Currently, if there is an app with notificationProvider permission, 134 // notifications will go to both message center and the app. 135 // Change it to send notifications to message center only when the user chose 136 // default message center (extension_id.empty()). 137 138 // If there exist apps/extensions that have notificationProvider permission, 139 // route notifications to one of the apps/extensions. 140 std::string extension_id = GetExtensionTakingOverNotifications(profile); 141 if (!extension_id.empty()) 142 profile_notification->AddToAlternateProvider(extension_id); 143 144 message_center_->AddNotification(make_scoped_ptr( 145 new message_center::Notification(profile_notification->notification()))); 146 profile_notification->StartDownloads(); 147} 148 149bool MessageCenterNotificationManager::Update(const Notification& notification, 150 Profile* profile) { 151 const base::string16& replace_id = notification.replace_id(); 152 if (replace_id.empty()) 153 return false; 154 155 const GURL origin_url = notification.origin_url(); 156 DCHECK(origin_url.is_valid()); 157 158 // Since replace_id is provided by arbitrary JS, we need to use origin_url 159 // (which is an app url in case of app/extension) to scope the replace ids 160 // in the given profile. 161 for (NotificationMap::iterator iter = profile_notifications_.begin(); 162 iter != profile_notifications_.end(); ++iter) { 163 ProfileNotification* old_notification = (*iter).second; 164 if (old_notification->notification().replace_id() == replace_id && 165 old_notification->notification().origin_url() == origin_url && 166 old_notification->profile() == profile) { 167 // Changing the type from non-progress to progress does not count towards 168 // the immediate update allowed in the message center. 169 std::string old_id = 170 old_notification->notification().delegate_id(); 171 172 // Add/remove notification in the local list but just update the same 173 // one in MessageCenter. 174 delete old_notification; 175 profile_notifications_.erase(old_id); 176 ProfileNotification* new_notification = 177 new ProfileNotification(profile, notification, message_center_); 178 profile_notifications_[notification.delegate_id()] = new_notification; 179 180 // TODO(liyanhou): Add routing updated notifications to alternative 181 // providers. 182 183 // WARNING: You MUST use AddProfileNotification or update the message 184 // center via the notification within a ProfileNotification object or the 185 // profile ID will not be correctly set for ChromeOS. 186 message_center_->UpdateNotification( 187 old_id, 188 make_scoped_ptr(new message_center::Notification( 189 new_notification->notification()))); 190 191 new_notification->StartDownloads(); 192 return true; 193 } 194 } 195 return false; 196} 197 198const Notification* MessageCenterNotificationManager::FindById( 199 const std::string& id) const { 200 NotificationMap::const_iterator iter = profile_notifications_.find(id); 201 if (iter == profile_notifications_.end()) 202 return NULL; 203 return &(iter->second->notification()); 204} 205 206bool MessageCenterNotificationManager::CancelById(const std::string& id) { 207 // See if this ID hasn't been shown yet. 208 // If it has been shown, remove it. 209 NotificationMap::iterator iter = profile_notifications_.find(id); 210 if (iter == profile_notifications_.end()) 211 return false; 212 213 RemoveProfileNotification(iter->second); 214 message_center_->RemoveNotification(id, /* by_user */ false); 215 return true; 216} 217 218std::set<std::string> 219MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin( 220 Profile* profile, 221 const GURL& source) { 222 223 std::set<std::string> notification_ids; 224 for (NotificationMap::iterator iter = profile_notifications_.begin(); 225 iter != profile_notifications_.end(); iter++) { 226 if ((*iter).second->notification().origin_url() == source && 227 profile == (*iter).second->profile()) { 228 notification_ids.insert(iter->first); 229 } 230 } 231 return notification_ids; 232} 233 234bool MessageCenterNotificationManager::CancelAllBySourceOrigin( 235 const GURL& source) { 236 // Same pattern as CancelById, but more complicated than the above 237 // because there may be multiple notifications from the same source. 238 bool removed = false; 239 240 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 241 loopiter != profile_notifications_.end(); ) { 242 NotificationMap::iterator curiter = loopiter++; 243 if ((*curiter).second->notification().origin_url() == source) { 244 const std::string id = curiter->first; 245 RemoveProfileNotification(curiter->second); 246 message_center_->RemoveNotification(id, /* by_user */ false); 247 removed = true; 248 } 249 } 250 return removed; 251} 252 253bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) { 254 // Same pattern as CancelAllBySourceOrigin. 255 bool removed = false; 256 257 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 258 loopiter != profile_notifications_.end(); ) { 259 NotificationMap::iterator curiter = loopiter++; 260 if (profile == (*curiter).second->profile()) { 261 const std::string id = curiter->first; 262 RemoveProfileNotification(curiter->second); 263 message_center_->RemoveNotification(id, /* by_user */ false); 264 removed = true; 265 } 266 } 267 return removed; 268} 269 270void MessageCenterNotificationManager::CancelAll() { 271 message_center_->RemoveAllNotifications(/* by_user */ false); 272} 273 274//////////////////////////////////////////////////////////////////////////////// 275// MessageCenter::Observer 276void MessageCenterNotificationManager::OnNotificationRemoved( 277 const std::string& notification_id, 278 bool by_user) { 279 NotificationMap::const_iterator iter = 280 profile_notifications_.find(notification_id); 281 if (iter != profile_notifications_.end()) 282 RemoveProfileNotification(iter->second); 283 284#if defined(OS_WIN) 285 CheckFirstRunTimer(); 286#endif 287} 288 289void MessageCenterNotificationManager::OnCenterVisibilityChanged( 290 message_center::Visibility visibility) { 291#if defined(OS_WIN) 292 if (visibility == message_center::VISIBILITY_TRANSIENT) 293 CheckFirstRunTimer(); 294#endif 295} 296 297void MessageCenterNotificationManager::OnNotificationUpdated( 298 const std::string& notification_id) { 299#if defined(OS_WIN) 300 CheckFirstRunTimer(); 301#endif 302} 303 304void MessageCenterNotificationManager::EnsureMessageCenterClosed() { 305 if (tray_.get()) 306 tray_->GetMessageCenterTray()->HideMessageCenterBubble(); 307 308#if defined(USE_ASH) 309 if (ash::Shell::HasInstance()) { 310 ash::WebNotificationTray* tray = 311 ash::Shell::GetInstance()->GetWebNotificationTray(); 312 if (tray) 313 tray->GetMessageCenterTray()->HideMessageCenterBubble(); 314 } 315#endif 316} 317 318void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest( 319 message_center::MessageCenterTrayDelegate* delegate) { 320 tray_.reset(delegate); 321} 322 323void MessageCenterNotificationManager::Observe( 324 int type, 325 const content::NotificationSource& source, 326 const content::NotificationDetails& details) { 327 if (type == chrome::NOTIFICATION_FULLSCREEN_CHANGED) { 328 const bool is_fullscreen = *content::Details<bool>(details).ptr(); 329 330 if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray()) 331 tray_->GetMessageCenterTray()->HidePopupBubble(); 332 } 333} 334 335//////////////////////////////////////////////////////////////////////////////// 336// ImageDownloads 337 338MessageCenterNotificationManager::ImageDownloads::ImageDownloads( 339 message_center::MessageCenter* message_center, 340 ImageDownloadsObserver* observer) 341 : message_center_(message_center), 342 pending_downloads_(0), 343 observer_(observer) { 344} 345 346MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { } 347 348void MessageCenterNotificationManager::ImageDownloads::StartDownloads( 349 const Notification& notification) { 350 // In case all downloads are synchronous, assume a pending download. 351 AddPendingDownload(); 352 353 // Notification primary icon. 354 StartDownloadWithImage( 355 notification, 356 ¬ification.icon(), 357 notification.icon_url(), 358 base::Bind(&message_center::MessageCenter::SetNotificationIcon, 359 base::Unretained(message_center_), 360 notification.delegate_id())); 361 362 // Notification image. 363 StartDownloadWithImage( 364 notification, 365 NULL, 366 notification.image_url(), 367 base::Bind(&message_center::MessageCenter::SetNotificationImage, 368 base::Unretained(message_center_), 369 notification.delegate_id())); 370 371 // Notification button icons. 372 StartDownloadWithImage( 373 notification, 374 NULL, 375 notification.button_one_icon_url(), 376 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 377 base::Unretained(message_center_), 378 notification.delegate_id(), 379 0)); 380 StartDownloadWithImage( 381 notification, 382 NULL, 383 notification.button_two_icon_url(), 384 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 385 base::Unretained(message_center_), 386 notification.delegate_id(), 387 1)); 388 389 // This should tell the observer we're done if everything was synchronous. 390 PendingDownloadCompleted(); 391} 392 393void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage( 394 const Notification& notification, 395 const gfx::Image* image, 396 const GURL& url, 397 const SetImageCallback& callback) { 398 // Set the image directly if we have it. 399 if (image && !image->IsEmpty()) { 400 callback.Run(*image); 401 return; 402 } 403 404 // Leave the image null if there's no URL. 405 if (url.is_empty()) 406 return; 407 408 content::WebContents* contents = notification.GetWebContents(); 409 if (!contents) { 410 LOG(WARNING) << "Notification needs an image but has no WebContents"; 411 return; 412 } 413 414 AddPendingDownload(); 415 416 contents->DownloadImage( 417 url, 418 false, // Not a favicon 419 0, // No maximum size 420 base::Bind( 421 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete, 422 AsWeakPtr(), 423 callback)); 424} 425 426void MessageCenterNotificationManager::ImageDownloads::DownloadComplete( 427 const SetImageCallback& callback, 428 int download_id, 429 int http_status_code, 430 const GURL& image_url, 431 const std::vector<SkBitmap>& bitmaps, 432 const std::vector<gfx::Size>& original_bitmap_sizes) { 433 PendingDownloadCompleted(); 434 435 if (bitmaps.empty()) 436 return; 437 gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]); 438 callback.Run(image); 439} 440 441// Private methods. 442 443void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() { 444 ++pending_downloads_; 445} 446 447void 448MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() { 449 DCHECK(pending_downloads_ > 0); 450 if (--pending_downloads_ == 0 && observer_) 451 observer_->OnDownloadsCompleted(); 452} 453 454//////////////////////////////////////////////////////////////////////////////// 455// ProfileNotification 456 457MessageCenterNotificationManager::ProfileNotification::ProfileNotification( 458 Profile* profile, 459 const Notification& notification, 460 message_center::MessageCenter* message_center) 461 : profile_(profile), 462 notification_(notification), 463 downloads_(new ImageDownloads(message_center, this)) { 464 DCHECK(profile); 465#if defined(OS_CHROMEOS) 466 notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile)); 467#endif 468} 469 470MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() { 471} 472 473void MessageCenterNotificationManager::ProfileNotification::StartDownloads() { 474 downloads_->StartDownloads(notification_); 475} 476 477void 478MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() { 479 notification_.DoneRendering(); 480} 481 482std::string 483 MessageCenterNotificationManager::ProfileNotification::GetExtensionId() { 484 extensions::InfoMap* extension_info_map = 485 extensions::ExtensionSystem::Get(profile())->info_map(); 486 extensions::ExtensionSet extensions; 487 extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin( 488 notification().origin_url(), 489 notification().process_id(), 490 extensions::APIPermission::kNotifications, 491 &extensions); 492 493 DesktopNotificationService* desktop_service = 494 DesktopNotificationServiceFactory::GetForProfile(profile()); 495 for (extensions::ExtensionSet::const_iterator iter = extensions.begin(); 496 iter != extensions.end(); ++iter) { 497 if (desktop_service->IsNotifierEnabled(message_center::NotifierId( 498 message_center::NotifierId::APPLICATION, (*iter)->id()))) { 499 return (*iter)->id(); 500 } 501 } 502 return std::string(); 503} 504 505void 506MessageCenterNotificationManager::ProfileNotification::AddToAlternateProvider( 507 const std::string extension_id) { 508 // Convert data from Notification type to NotificationOptions type. 509 extensions::api::notifications::NotificationOptions options; 510 NotificationConversionHelper::NotificationToNotificationOptions(notification_, 511 &options); 512 513 // Send the notification to the alternate provider extension/app. 514 extensions::NotificationProviderEventRouter event_router(profile_); 515 event_router.CreateNotification(extension_id, 516 notification_.notifier_id().id, 517 notification_.delegate_id(), 518 options); 519} 520 521//////////////////////////////////////////////////////////////////////////////// 522// private 523 524void MessageCenterNotificationManager::AddProfileNotification( 525 ProfileNotification* profile_notification) { 526 std::string id = profile_notification->notification().delegate_id(); 527 // Notification ids should be unique. 528 DCHECK(profile_notifications_.find(id) == profile_notifications_.end()); 529 profile_notifications_[id] = profile_notification; 530} 531 532void MessageCenterNotificationManager::RemoveProfileNotification( 533 ProfileNotification* profile_notification) { 534 std::string id = profile_notification->notification().delegate_id(); 535 profile_notifications_.erase(id); 536 delete profile_notification; 537} 538 539MessageCenterNotificationManager::ProfileNotification* 540 MessageCenterNotificationManager::FindProfileNotification( 541 const std::string& id) const { 542 NotificationMap::const_iterator iter = profile_notifications_.find(id); 543 if (iter == profile_notifications_.end()) 544 return NULL; 545 546 return (*iter).second; 547} 548 549std::string 550MessageCenterNotificationManager::GetExtensionTakingOverNotifications( 551 Profile* profile) { 552 // TODO(liyanhou): When additional settings in Chrome Settings is implemented, 553 // change choosing the last app with permission to a user selected app. 554 extensions::ExtensionRegistry* registry = 555 extensions::ExtensionRegistry::Get(profile); 556 DCHECK(registry); 557 std::string extension_id; 558 for (extensions::ExtensionSet::const_iterator it = 559 registry->enabled_extensions().begin(); 560 it != registry->enabled_extensions().end(); 561 ++it) { 562 if ((*it->get()).permissions_data()->HasAPIPermission( 563 extensions::APIPermission::ID::kNotificationProvider)) { 564 extension_id = (*it->get()).id(); 565 return extension_id; 566 } 567 } 568 return extension_id; 569} 570