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