message_center_notification_manager.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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 28MessageCenterNotificationManager::MessageCenterNotificationManager( 29 message_center::MessageCenter* message_center) 30 : message_center_(message_center), 31 settings_controller_(new MessageCenterSettingsController) { 32 message_center_->SetDelegate(this); 33 message_center_->AddObserver(this); 34 35#if defined(OS_WIN) || defined(OS_MACOSX) 36 // On Windows and Mac, the notification manager owns the tray icon and views. 37 // Other platforms have global ownership and Create will return NULL. 38 tray_.reset(message_center::CreateMessageCenterTray()); 39#endif 40} 41 42MessageCenterNotificationManager::~MessageCenterNotificationManager() { 43 message_center_->RemoveObserver(this); 44} 45 46 47//////////////////////////////////////////////////////////////////////////////// 48// NotificationUIManager 49 50bool MessageCenterNotificationManager::DoesIdExist(const std::string& id) { 51 if (NotificationUIManagerImpl::DoesIdExist(id)) 52 return true; 53 NotificationMap::iterator iter = profile_notifications_.find(id); 54 if (iter == profile_notifications_.end()) 55 return false; 56 return true; 57} 58 59bool MessageCenterNotificationManager::CancelById(const std::string& id) { 60 // See if this ID hasn't been shown yet. 61 if (NotificationUIManagerImpl::CancelById(id)) 62 return true; 63 64 // If it has been shown, remove it. 65 NotificationMap::iterator iter = profile_notifications_.find(id); 66 if (iter == profile_notifications_.end()) 67 return false; 68 69 message_center_->RemoveNotification(id, /* by_user */ false); 70 return true; 71} 72 73bool MessageCenterNotificationManager::CancelAllBySourceOrigin( 74 const GURL& source) { 75 // Same pattern as CancelById, but more complicated than the above 76 // because there may be multiple notifications from the same source. 77 bool removed = NotificationUIManagerImpl::CancelAllBySourceOrigin(source); 78 79 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 80 loopiter != profile_notifications_.end(); ) { 81 NotificationMap::iterator curiter = loopiter++; 82 if ((*curiter).second->notification().origin_url() == source) { 83 message_center_->RemoveNotification(curiter->first, /* by_user */ false); 84 removed = true; 85 } 86 } 87 return removed; 88} 89 90bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) { 91 // Same pattern as CancelAllBySourceOrigin. 92 bool removed = NotificationUIManagerImpl::CancelAllByProfile(profile); 93 94 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 95 loopiter != profile_notifications_.end(); ) { 96 NotificationMap::iterator curiter = loopiter++; 97 if ((*curiter).second->profile() == profile) { 98 message_center_->RemoveNotification(curiter->first, /* by_user */ false); 99 removed = true; 100 } 101 } 102 return removed; 103} 104 105void MessageCenterNotificationManager::CancelAll() { 106 NotificationUIManagerImpl::CancelAll(); 107 108 message_center_->RemoveAllNotifications(/* by_user */ false); 109} 110 111//////////////////////////////////////////////////////////////////////////////// 112// NotificationUIManagerImpl 113 114bool MessageCenterNotificationManager::ShowNotification( 115 const Notification& notification, Profile* profile) { 116 // There is always space in MessageCenter, it never rejects Notifications. 117 AddProfileNotification( 118 new ProfileNotification(profile, notification, message_center_)); 119 return true; 120} 121 122bool MessageCenterNotificationManager::UpdateNotification( 123 const Notification& notification, Profile* profile) { 124 const string16& replace_id = notification.replace_id(); 125 DCHECK(!replace_id.empty()); 126 const GURL origin_url = notification.origin_url(); 127 DCHECK(origin_url.is_valid()); 128 129 // Since replace_id is provided by arbitrary JS, we need to use origin_url 130 // (which is an app url in case of app/extension) to scope the replace ids 131 // in the given profile. 132 for (NotificationMap::iterator iter = profile_notifications_.begin(); 133 iter != profile_notifications_.end(); ++iter) { 134 ProfileNotification* old_notification = (*iter).second; 135 if (old_notification->notification().replace_id() == replace_id && 136 old_notification->notification().origin_url() == origin_url && 137 old_notification->profile()->IsSameProfile(profile)) { 138 std::string old_id = 139 old_notification->notification().notification_id(); 140 DCHECK(message_center_->HasNotification(old_id)); 141 142 // Add/remove notification in the local list but just update the same 143 // one in MessageCenter. 144 old_notification->notification().Close(false); // Not by user. 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.notification_id()] = new_notification; 150 message_center_->UpdateNotification(old_id, 151 notification.notification_id(), 152 notification.title(), 153 notification.body(), 154 notification.optional_fields(), 155 notification.delegate()); 156 new_notification->StartDownloads(); 157 return true; 158 } 159 } 160 return false; 161} 162 163//////////////////////////////////////////////////////////////////////////////// 164// MessageCenter::Delegate 165 166void MessageCenterNotificationManager::DisableExtension( 167 const std::string& notification_id) { 168 ProfileNotification* profile_notification = 169 FindProfileNotification(notification_id); 170 if (!profile_notification) 171 return; 172 173 std::string extension_id = profile_notification->GetExtensionId(); 174 DCHECK(!extension_id.empty()); // or UI should not have enabled the command. 175 DesktopNotificationService* service = 176 DesktopNotificationServiceFactory::GetForProfile( 177 profile_notification->profile()); 178 service->SetExtensionEnabled(extension_id, false); 179} 180 181void MessageCenterNotificationManager::DisableNotificationsFromSource( 182 const std::string& notification_id) { 183 ProfileNotification* profile_notification = 184 FindProfileNotification(notification_id); 185 if (!profile_notification) 186 return; 187 188 // UI should not have enabled the command if there is no valid source. 189 DCHECK(profile_notification->notification().origin_url().is_valid()); 190 DesktopNotificationService* service = 191 DesktopNotificationServiceFactory::GetForProfile( 192 profile_notification->profile()); 193 if (profile_notification->notification().origin_url().scheme() == 194 chrome::kChromeUIScheme) { 195 const std::string name = 196 profile_notification->notification().origin_url().host(); 197 const message_center::Notifier::SystemComponentNotifierType type = 198 message_center::ParseSystemComponentName(name); 199 service->SetSystemComponentEnabled(type, false); 200 } else { 201 service->DenyPermission(profile_notification->notification().origin_url()); 202 } 203} 204 205void MessageCenterNotificationManager::ShowSettings( 206 const std::string& notification_id) { 207 // The per-message-center Settings button passes an empty string. 208 if (notification_id.empty()) { 209 NOTIMPLEMENTED(); 210 return; 211 } 212 213 ProfileNotification* profile_notification = 214 FindProfileNotification(notification_id); 215 if (!profile_notification) 216 return; 217 218 Browser* browser = 219 chrome::FindOrCreateTabbedBrowser(profile_notification->profile(), 220 chrome::HOST_DESKTOP_TYPE_NATIVE); 221 if (profile_notification->GetExtensionId().empty()) 222 chrome::ShowContentSettings(browser, CONTENT_SETTINGS_TYPE_NOTIFICATIONS); 223 else 224 chrome::ShowExtensions(browser, std::string()); 225} 226 227void MessageCenterNotificationManager::ShowSettingsDialog( 228 gfx::NativeView context) { 229 settings_controller_->ShowSettingsDialog(context); 230} 231 232//////////////////////////////////////////////////////////////////////////////// 233// MessageCenter::Observer 234void MessageCenterNotificationManager::OnNotificationRemoved( 235 const std::string& notification_id, 236 bool by_user) { 237 // Do not call FindProfileNotification(). Some tests create notifications 238 // directly to MessageCenter, but this method will be called for the removals 239 // of such notifications. 240 NotificationMap::const_iterator iter = 241 profile_notifications_.find(notification_id); 242 if (iter != profile_notifications_.end()) 243 RemoveProfileNotification(iter->second, by_user); 244} 245 246//////////////////////////////////////////////////////////////////////////////// 247// ImageDownloads 248 249MessageCenterNotificationManager::ImageDownloads::ImageDownloads( 250 message_center::MessageCenter* message_center, 251 ImageDownloadsObserver* observer) 252 : message_center_(message_center), 253 pending_downloads_(0), 254 observer_(observer) { 255} 256 257MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { } 258 259void MessageCenterNotificationManager::ImageDownloads::StartDownloads( 260 const Notification& notification) { 261 // In case all downloads are synchronous, assume a pending download. 262 AddPendingDownload(); 263 264 // Notification primary icon. 265 StartDownloadWithImage( 266 notification, 267 ¬ification.icon(), 268 notification.icon_url(), 269 message_center::kNotificationIconSize, 270 base::Bind(&message_center::MessageCenter::SetNotificationIcon, 271 base::Unretained(message_center_), 272 notification.notification_id())); 273 274 // Notification image. 275 StartDownloadByKey( 276 notification, 277 message_center::kImageUrlKey, 278 message_center::kNotificationPreferredImageSize, 279 base::Bind(&message_center::MessageCenter::SetNotificationImage, 280 base::Unretained(message_center_), 281 notification.notification_id())); 282 283 // Notification button icons. 284 StartDownloadByKey( 285 notification, 286 message_center::kButtonOneIconUrlKey, 287 message_center::kNotificationButtonIconSize, 288 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 289 base::Unretained(message_center_), 290 notification.notification_id(), 0)); 291 StartDownloadByKey( 292 notification, message_center::kButtonTwoIconUrlKey, 293 message_center::kNotificationButtonIconSize, 294 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 295 base::Unretained(message_center_), 296 notification.notification_id(), 1)); 297 298 // This should tell the observer we're done if everything was synchronous. 299 PendingDownloadCompleted(); 300} 301 302void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage( 303 const Notification& notification, 304 const gfx::Image* image, 305 const GURL& url, 306 int size, 307 const SetImageCallback& callback) { 308 // Set the image directly if we have it. 309 if (image && !image->IsEmpty()) { 310 callback.Run(*image); 311 return; 312 } 313 314 // Leave the image null if there's no URL. 315 if (url.is_empty()) 316 return; 317 318 content::RenderViewHost* host = notification.GetRenderViewHost(); 319 if (!host) { 320 LOG(WARNING) << "Notification needs an image but has no RenderViewHost"; 321 return; 322 } 323 324 content::WebContents* contents = 325 content::WebContents::FromRenderViewHost(host); 326 if (!contents) { 327 LOG(WARNING) << "Notification needs an image but has no WebContents"; 328 return; 329 } 330 331 AddPendingDownload(); 332 333 contents->DownloadImage( 334 url, 335 false, 336 size, 337 base::Bind( 338 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete, 339 AsWeakPtr(), 340 callback)); 341} 342 343void MessageCenterNotificationManager::ImageDownloads::StartDownloadByKey( 344 const Notification& notification, 345 const char* key, 346 int size, 347 const SetImageCallback& callback) { 348 const base::DictionaryValue* optional_fields = notification.optional_fields(); 349 if (optional_fields && optional_fields->HasKey(key)) { 350 string16 url; 351 optional_fields->GetString(key, &url); 352 StartDownloadWithImage(notification, NULL, GURL(url), size, callback); 353 } 354} 355 356void MessageCenterNotificationManager::ImageDownloads::DownloadComplete( 357 const SetImageCallback& callback, 358 int download_id, 359 int http_status_code, 360 const GURL& image_url, 361 int requested_size, 362 const std::vector<SkBitmap>& bitmaps) { 363 PendingDownloadCompleted(); 364 365 if (bitmaps.empty()) 366 return; 367 gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]); 368 callback.Run(image); 369} 370 371// Private methods. 372 373void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() { 374 ++pending_downloads_; 375} 376 377void 378MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() { 379 DCHECK(pending_downloads_ > 0); 380 if (--pending_downloads_ == 0 && observer_) 381 observer_->OnDownloadsCompleted(); 382} 383 384//////////////////////////////////////////////////////////////////////////////// 385// ProfileNotification 386 387MessageCenterNotificationManager::ProfileNotification::ProfileNotification( 388 Profile* profile, 389 const Notification& notification, 390 message_center::MessageCenter* message_center) 391 : profile_(profile), 392 notification_(notification), 393 downloads_(new ImageDownloads(message_center, this)) { 394 DCHECK(profile); 395} 396 397MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() { 398} 399 400void MessageCenterNotificationManager::ProfileNotification::StartDownloads() { 401 downloads_->StartDownloads(notification_); 402} 403 404void 405MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() { 406 notification_.DoneRendering(); 407} 408 409std::string 410 MessageCenterNotificationManager::ProfileNotification::GetExtensionId() { 411 ExtensionInfoMap* extension_info_map = 412 extensions::ExtensionSystem::Get(profile())->info_map(); 413 ExtensionSet extensions; 414 extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin( 415 notification().origin_url(), notification().process_id(), 416 extensions::APIPermission::kNotification, &extensions); 417 418 DesktopNotificationService* desktop_service = 419 DesktopNotificationServiceFactory::GetForProfile(profile()); 420 for (ExtensionSet::const_iterator iter = extensions.begin(); 421 iter != extensions.end(); ++iter) { 422 if (desktop_service->IsExtensionEnabled((*iter)->id())) 423 return (*iter)->id(); 424 } 425 return std::string(); 426} 427 428//////////////////////////////////////////////////////////////////////////////// 429// private 430 431void MessageCenterNotificationManager::AddProfileNotification( 432 ProfileNotification* profile_notification) { 433 const Notification& notification = profile_notification->notification(); 434 std::string id = notification.notification_id(); 435 // Notification ids should be unique. 436 DCHECK(profile_notifications_.find(id) == profile_notifications_.end()); 437 profile_notifications_[id] = profile_notification; 438 439 message_center_->AddNotification(notification.type(), 440 notification.notification_id(), 441 notification.title(), 442 notification.body(), 443 notification.display_source(), 444 profile_notification->GetExtensionId(), 445 notification.optional_fields(), 446 notification.delegate()); 447 profile_notification->StartDownloads(); 448} 449 450void MessageCenterNotificationManager::RemoveProfileNotification( 451 ProfileNotification* profile_notification, 452 bool by_user) { 453 profile_notification->notification().Close(by_user); 454 std::string id = profile_notification->notification().notification_id(); 455 profile_notifications_.erase(id); 456 delete profile_notification; 457} 458 459MessageCenterNotificationManager::ProfileNotification* 460 MessageCenterNotificationManager::FindProfileNotification( 461 const std::string& id) const { 462 NotificationMap::const_iterator iter = profile_notifications_.find(id); 463 if (iter == profile_notifications_.end()) 464 return NULL; 465 466 return (*iter).second; 467} 468