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