message_center_notification_manager.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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_service.h" 10#include "chrome/browser/chrome_notification_types.h" 11#include "chrome/browser/notifications/desktop_notification_service.h" 12#include "chrome/browser/notifications/desktop_notification_service_factory.h" 13#include "chrome/browser/notifications/fullscreen_notification_blocker.h" 14#include "chrome/browser/notifications/message_center_settings_controller.h" 15#include "chrome/browser/notifications/notification.h" 16#include "chrome/browser/notifications/screen_lock_notification_blocker.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/browser/ui/browser_finder.h" 19#include "chrome/browser/ui/chrome_pages.h" 20#include "chrome/browser/ui/host_desktop.h" 21#include "chrome/common/pref_names.h" 22#include "content/public/browser/notification_service.h" 23#include "content/public/browser/web_contents.h" 24#include "content/public/common/url_constants.h" 25#include "extensions/browser/extension_system.h" 26#include "extensions/browser/info_map.h" 27#include "extensions/common/extension_set.h" 28#include "ui/gfx/image/image_skia.h" 29#include "ui/message_center/message_center_style.h" 30#include "ui/message_center/message_center_tray.h" 31#include "ui/message_center/message_center_types.h" 32#include "ui/message_center/notifier_settings.h" 33 34#if defined(OS_CHROMEOS) 35#include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h" 36#include "chrome/browser/ui/ash/multi_user/multi_user_util.h" 37#endif 38 39#if defined(USE_ASH) 40#include "ash/shell.h" 41#include "ash/system/web_notification/web_notification_tray.h" 42#endif 43 44#if defined(OS_WIN) 45// The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all 46// popups go away and the user has notifications in the message center. 47const int kFirstRunIdleDelaySeconds = 1; 48#endif 49 50MessageCenterNotificationManager::MessageCenterNotificationManager( 51 message_center::MessageCenter* message_center, 52 PrefService* local_state, 53 scoped_ptr<message_center::NotifierSettingsProvider> settings_provider) 54 : message_center_(message_center), 55#if defined(OS_WIN) 56 first_run_idle_timeout_( 57 base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)), 58 weak_factory_(this), 59#endif 60 settings_provider_(settings_provider.Pass()), 61 system_observer_(this), 62 stats_collector_(message_center) { 63#if defined(OS_WIN) 64 first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state); 65#endif 66 67 message_center_->AddObserver(this); 68 message_center_->SetNotifierSettingsProvider(settings_provider_.get()); 69 70#if defined(OS_CHROMEOS) 71 blockers_.push_back( 72 new LoginStateNotificationBlockerChromeOS(message_center)); 73#else 74 blockers_.push_back(new ScreenLockNotificationBlocker(message_center)); 75#endif 76 blockers_.push_back(new FullscreenNotificationBlocker(message_center)); 77 78#if defined(OS_WIN) || defined(OS_MACOSX) \ 79 || (defined(USE_AURA) && !defined(USE_ASH)) 80 // On Windows, Linux and Mac, the notification manager owns the tray icon and 81 // views.Other platforms have global ownership and Create will return NULL. 82 tray_.reset(message_center::CreateMessageCenterTray()); 83#endif 84 registrar_.Add(this, 85 chrome::NOTIFICATION_FULLSCREEN_CHANGED, 86 content::NotificationService::AllSources()); 87} 88 89MessageCenterNotificationManager::~MessageCenterNotificationManager() { 90 message_center_->RemoveObserver(this); 91} 92 93//////////////////////////////////////////////////////////////////////////////// 94// NotificationUIManager 95 96void MessageCenterNotificationManager::Add(const Notification& notification, 97 Profile* profile) { 98 if (Update(notification, profile)) 99 return; 100 101 DesktopNotificationServiceFactory::GetForProfile(profile)-> 102 ShowWelcomeNotificationIfNecessary(notification); 103 104 AddProfileNotification( 105 new ProfileNotification(profile, notification, message_center_)); 106} 107 108bool MessageCenterNotificationManager::Update(const Notification& notification, 109 Profile* profile) { 110 const base::string16& replace_id = notification.replace_id(); 111 if (replace_id.empty()) 112 return false; 113 114 const GURL origin_url = notification.origin_url(); 115 DCHECK(origin_url.is_valid()); 116 117 // Since replace_id is provided by arbitrary JS, we need to use origin_url 118 // (which is an app url in case of app/extension) to scope the replace ids 119 // in the given profile. 120 for (NotificationMap::iterator iter = profile_notifications_.begin(); 121 iter != profile_notifications_.end(); ++iter) { 122 ProfileNotification* old_notification = (*iter).second; 123 if (old_notification->notification().replace_id() == replace_id && 124 old_notification->notification().origin_url() == origin_url && 125 old_notification->profile() == profile) { 126 // Changing the type from non-progress to progress does not count towards 127 // the immediate update allowed in the message center. 128 std::string old_id = 129 old_notification->notification().notification_id(); 130 DCHECK(message_center_->HasNotification(old_id)); 131 132 // Add/remove notification in the local list but just update the same 133 // one in MessageCenter. 134 delete old_notification; 135 profile_notifications_.erase(old_id); 136 ProfileNotification* new_notification = 137 new ProfileNotification(profile, notification, message_center_); 138 profile_notifications_[notification.notification_id()] = new_notification; 139 140 // Now pass a copy to message center. 141 scoped_ptr<message_center::Notification> message_center_notification( 142 make_scoped_ptr(new message_center::Notification(notification))); 143 message_center_->UpdateNotification(old_id, 144 message_center_notification.Pass()); 145 146 new_notification->StartDownloads(); 147 return true; 148 } 149 } 150 return false; 151} 152 153const Notification* MessageCenterNotificationManager::FindById( 154 const std::string& id) const { 155 NotificationMap::const_iterator iter = profile_notifications_.find(id); 156 if (iter == profile_notifications_.end()) 157 return NULL; 158 return &(iter->second->notification()); 159} 160 161bool MessageCenterNotificationManager::CancelById(const std::string& id) { 162 // See if this ID hasn't been shown yet. 163 // If it has been shown, remove it. 164 NotificationMap::iterator iter = profile_notifications_.find(id); 165 if (iter == profile_notifications_.end()) 166 return false; 167 168 RemoveProfileNotification(iter->second); 169 message_center_->RemoveNotification(id, /* by_user */ false); 170 return true; 171} 172 173std::set<std::string> 174MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin( 175 Profile* profile, 176 const GURL& source) { 177 178 std::set<std::string> notification_ids; 179 for (NotificationMap::iterator iter = profile_notifications_.begin(); 180 iter != profile_notifications_.end(); iter++) { 181 if ((*iter).second->notification().origin_url() == source && 182 profile == (*iter).second->profile()) { 183 notification_ids.insert(iter->first); 184 } 185 } 186 return notification_ids; 187} 188 189bool MessageCenterNotificationManager::CancelAllBySourceOrigin( 190 const GURL& source) { 191 // Same pattern as CancelById, but more complicated than the above 192 // because there may be multiple notifications from the same source. 193 bool removed = false; 194 195 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 196 loopiter != profile_notifications_.end(); ) { 197 NotificationMap::iterator curiter = loopiter++; 198 if ((*curiter).second->notification().origin_url() == source) { 199 const std::string id = curiter->first; 200 RemoveProfileNotification(curiter->second); 201 message_center_->RemoveNotification(id, /* by_user */ false); 202 removed = true; 203 } 204 } 205 return removed; 206} 207 208bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) { 209 // Same pattern as CancelAllBySourceOrigin. 210 bool removed = false; 211 212 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 213 loopiter != profile_notifications_.end(); ) { 214 NotificationMap::iterator curiter = loopiter++; 215 if (profile == (*curiter).second->profile()) { 216 const std::string id = curiter->first; 217 RemoveProfileNotification(curiter->second); 218 message_center_->RemoveNotification(id, /* by_user */ false); 219 removed = true; 220 } 221 } 222 return removed; 223} 224 225void MessageCenterNotificationManager::CancelAll() { 226 message_center_->RemoveAllNotifications(/* by_user */ false); 227} 228 229//////////////////////////////////////////////////////////////////////////////// 230// MessageCenter::Observer 231void MessageCenterNotificationManager::OnNotificationRemoved( 232 const std::string& notification_id, 233 bool by_user) { 234 NotificationMap::const_iterator iter = 235 profile_notifications_.find(notification_id); 236 if (iter != profile_notifications_.end()) 237 RemoveProfileNotification(iter->second); 238 239#if defined(OS_WIN) 240 CheckFirstRunTimer(); 241#endif 242} 243 244void MessageCenterNotificationManager::OnCenterVisibilityChanged( 245 message_center::Visibility visibility) { 246#if defined(OS_WIN) 247 if (visibility == message_center::VISIBILITY_TRANSIENT) 248 CheckFirstRunTimer(); 249#endif 250} 251 252void MessageCenterNotificationManager::OnNotificationUpdated( 253 const std::string& notification_id) { 254#if defined(OS_WIN) 255 CheckFirstRunTimer(); 256#endif 257} 258 259void MessageCenterNotificationManager::EnsureMessageCenterClosed() { 260 if (tray_.get()) 261 tray_->GetMessageCenterTray()->HideMessageCenterBubble(); 262 263#if defined(USE_ASH) 264 if (ash::Shell::HasInstance()) { 265 ash::WebNotificationTray* tray = 266 ash::Shell::GetInstance()->GetWebNotificationTray(); 267 if (tray) 268 tray->GetMessageCenterTray()->HideMessageCenterBubble(); 269 } 270#endif 271} 272 273void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest( 274 message_center::MessageCenterTrayDelegate* delegate) { 275 tray_.reset(delegate); 276} 277 278void MessageCenterNotificationManager::Observe( 279 int type, 280 const content::NotificationSource& source, 281 const content::NotificationDetails& details) { 282 if (type == chrome::NOTIFICATION_FULLSCREEN_CHANGED) { 283 const bool is_fullscreen = *content::Details<bool>(details).ptr(); 284 285 if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray()) 286 tray_->GetMessageCenterTray()->HidePopupBubble(); 287 } 288} 289 290//////////////////////////////////////////////////////////////////////////////// 291// ImageDownloads 292 293MessageCenterNotificationManager::ImageDownloads::ImageDownloads( 294 message_center::MessageCenter* message_center, 295 ImageDownloadsObserver* observer) 296 : message_center_(message_center), 297 pending_downloads_(0), 298 observer_(observer) { 299} 300 301MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { } 302 303void MessageCenterNotificationManager::ImageDownloads::StartDownloads( 304 const Notification& notification) { 305 // In case all downloads are synchronous, assume a pending download. 306 AddPendingDownload(); 307 308 // Notification primary icon. 309 StartDownloadWithImage( 310 notification, 311 ¬ification.icon(), 312 notification.icon_url(), 313 base::Bind(&message_center::MessageCenter::SetNotificationIcon, 314 base::Unretained(message_center_), 315 notification.notification_id())); 316 317 // Notification image. 318 StartDownloadWithImage( 319 notification, 320 NULL, 321 notification.image_url(), 322 base::Bind(&message_center::MessageCenter::SetNotificationImage, 323 base::Unretained(message_center_), 324 notification.notification_id())); 325 326 // Notification button icons. 327 StartDownloadWithImage( 328 notification, 329 NULL, 330 notification.button_one_icon_url(), 331 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 332 base::Unretained(message_center_), 333 notification.notification_id(), 334 0)); 335 StartDownloadWithImage( 336 notification, 337 NULL, 338 notification.button_two_icon_url(), 339 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 340 base::Unretained(message_center_), 341 notification.notification_id(), 342 1)); 343 344 // This should tell the observer we're done if everything was synchronous. 345 PendingDownloadCompleted(); 346} 347 348void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage( 349 const Notification& notification, 350 const gfx::Image* image, 351 const GURL& url, 352 const SetImageCallback& callback) { 353 // Set the image directly if we have it. 354 if (image && !image->IsEmpty()) { 355 callback.Run(*image); 356 return; 357 } 358 359 // Leave the image null if there's no URL. 360 if (url.is_empty()) 361 return; 362 363 content::RenderViewHost* host = notification.GetRenderViewHost(); 364 if (!host) { 365 LOG(WARNING) << "Notification needs an image but has no RenderViewHost"; 366 return; 367 } 368 369 content::WebContents* contents = 370 content::WebContents::FromRenderViewHost(host); 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