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