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