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