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