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