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