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 "base/stl_util.h" 12#include "chrome/browser/extensions/api/notification_provider/notification_provider_api.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/extension_welcome_notification.h" 16#include "chrome/browser/notifications/extension_welcome_notification_factory.h" 17#include "chrome/browser/notifications/fullscreen_notification_blocker.h" 18#include "chrome/browser/notifications/message_center_settings_controller.h" 19#include "chrome/browser/notifications/notification.h" 20#include "chrome/browser/notifications/notification_conversion_helper.h" 21#include "chrome/browser/notifications/screen_lock_notification_blocker.h" 22#include "chrome/browser/profiles/profile.h" 23#include "chrome/common/extensions/api/notification_provider.h" 24#include "chrome/common/pref_names.h" 25#include "content/public/browser/web_contents.h" 26#include "content/public/common/url_constants.h" 27#include "extensions/browser/extension_registry.h" 28#include "extensions/common/extension_set.h" 29#include "extensions/common/permissions/permissions_data.h" 30#include "ui/gfx/image/image_skia.h" 31#include "ui/message_center/message_center_style.h" 32#include "ui/message_center/message_center_tray.h" 33#include "ui/message_center/message_center_types.h" 34#include "ui/message_center/notifier_settings.h" 35 36#if defined(OS_CHROMEOS) 37#include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h" 38#include "chrome/browser/ui/ash/multi_user/multi_user_util.h" 39#endif 40 41#if defined(USE_ASH) 42#include "ash/shell.h" 43#include "ash/system/web_notification/web_notification_tray.h" 44#endif 45 46#if defined(OS_WIN) 47// The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all 48// popups go away and the user has notifications in the message center. 49const int kFirstRunIdleDelaySeconds = 1; 50#endif 51 52MessageCenterNotificationManager::MessageCenterNotificationManager( 53 message_center::MessageCenter* message_center, 54 PrefService* local_state, 55 scoped_ptr<message_center::NotifierSettingsProvider> settings_provider) 56 : message_center_(message_center), 57#if defined(OS_WIN) 58 first_run_idle_timeout_( 59 base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)), 60 weak_factory_(this), 61#endif 62 settings_provider_(settings_provider.Pass()), 63 system_observer_(this), 64 stats_collector_(message_center), 65 google_now_stats_collector_(message_center) { 66#if defined(OS_WIN) 67 first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state); 68#endif 69 70 message_center_->AddObserver(this); 71 message_center_->SetNotifierSettingsProvider(settings_provider_.get()); 72 73#if defined(OS_CHROMEOS) 74#if !defined(USE_ATHENA) 75 // TODO(oshima|hashimoto): Support notification on athena. crbug.com/408755. 76 blockers_.push_back( 77 new LoginStateNotificationBlockerChromeOS(message_center)); 78#endif 79#else 80 blockers_.push_back(new ScreenLockNotificationBlocker(message_center)); 81#endif 82 blockers_.push_back(new FullscreenNotificationBlocker(message_center)); 83 84#if defined(OS_WIN) || defined(OS_MACOSX) \ 85 || (defined(OS_LINUX) && !defined(OS_CHROMEOS)) 86 // On Windows, Linux and Mac, the notification manager owns the tray icon and 87 // views.Other platforms have global ownership and Create will return NULL. 88 tray_.reset(message_center::CreateMessageCenterTray()); 89#endif 90} 91 92MessageCenterNotificationManager::~MessageCenterNotificationManager() { 93 message_center_->SetNotifierSettingsProvider(NULL); 94 message_center_->RemoveObserver(this); 95 96 STLDeleteContainerPairSecondPointers(profile_notifications_.begin(), 97 profile_notifications_.end()); 98 profile_notifications_.clear(); 99} 100 101void MessageCenterNotificationManager::RegisterPrefs( 102 PrefRegistrySimple* registry) { 103 registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon, 104 false); 105 registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true); 106 registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false); 107} 108 109//////////////////////////////////////////////////////////////////////////////// 110// NotificationUIManager 111 112void MessageCenterNotificationManager::Add(const Notification& notification, 113 Profile* profile) { 114 if (Update(notification, profile)) 115 return; 116 117 ExtensionWelcomeNotificationFactory::GetForBrowserContext(profile)-> 118 ShowWelcomeNotificationIfNecessary(notification); 119 120 // WARNING: You MUST update the message center via the notification within a 121 // ProfileNotification object or the profile ID will not be correctly set for 122 // ChromeOS. 123 ProfileNotification* profile_notification( 124 new ProfileNotification(profile, notification, message_center_)); 125 AddProfileNotification(profile_notification); 126 127 // TODO(liyanhou): Change the logic to only send notifications to one party. 128 // Currently, if there is an app with notificationProvider permission, 129 // notifications will go to both message center and the app. 130 // Change it to send notifications to message center only when the user chose 131 // default message center (extension_id.empty()). 132 133 // If there exist apps/extensions that have notificationProvider permission, 134 // route notifications to one of the apps/extensions. 135 std::string extension_id = GetExtensionTakingOverNotifications(profile); 136 if (!extension_id.empty()) 137 profile_notification->AddToAlternateProvider(extension_id); 138 139 message_center_->AddNotification(make_scoped_ptr( 140 new message_center::Notification(profile_notification->notification()))); 141 profile_notification->StartDownloads(); 142} 143 144bool MessageCenterNotificationManager::Update(const Notification& notification, 145 Profile* profile) { 146 const base::string16& replace_id = notification.replace_id(); 147 if (replace_id.empty()) 148 return false; 149 150 const GURL origin_url = notification.origin_url(); 151 DCHECK(origin_url.is_valid()); 152 153 // Since replace_id is provided by arbitrary JS, we need to use origin_url 154 // (which is an app url in case of app/extension) to scope the replace ids 155 // in the given profile. 156 for (NotificationMap::iterator iter = profile_notifications_.begin(); 157 iter != profile_notifications_.end(); ++iter) { 158 ProfileNotification* old_notification = (*iter).second; 159 if (old_notification->notification().replace_id() == replace_id && 160 old_notification->notification().origin_url() == origin_url && 161 old_notification->profile() == profile) { 162 // Changing the type from non-progress to progress does not count towards 163 // the immediate update allowed in the message center. 164 std::string old_id = 165 old_notification->notification().delegate_id(); 166 167 // Add/remove notification in the local list but just update the same 168 // one in MessageCenter. 169 delete old_notification; 170 profile_notifications_.erase(old_id); 171 ProfileNotification* new_notification = 172 new ProfileNotification(profile, notification, message_center_); 173 profile_notifications_[notification.delegate_id()] = new_notification; 174 175 // TODO(liyanhou): Add routing updated notifications to alternative 176 // providers. 177 178 // WARNING: You MUST use AddProfileNotification or update the message 179 // center via the notification within a ProfileNotification object or the 180 // profile ID will not be correctly set for ChromeOS. 181 message_center_->UpdateNotification( 182 old_id, 183 make_scoped_ptr(new message_center::Notification( 184 new_notification->notification()))); 185 186 new_notification->StartDownloads(); 187 return true; 188 } 189 } 190 return false; 191} 192 193const Notification* MessageCenterNotificationManager::FindById( 194 const std::string& id) const { 195 NotificationMap::const_iterator iter = profile_notifications_.find(id); 196 if (iter == profile_notifications_.end()) 197 return NULL; 198 return &(iter->second->notification()); 199} 200 201bool MessageCenterNotificationManager::CancelById(const std::string& id) { 202 // See if this ID hasn't been shown yet. 203 // If it has been shown, remove it. 204 NotificationMap::iterator iter = profile_notifications_.find(id); 205 if (iter == profile_notifications_.end()) 206 return false; 207 208 RemoveProfileNotification(iter->second); 209 message_center_->RemoveNotification(id, /* by_user */ false); 210 return true; 211} 212 213std::set<std::string> 214MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin( 215 Profile* profile, 216 const GURL& source) { 217 218 std::set<std::string> notification_ids; 219 for (NotificationMap::iterator iter = profile_notifications_.begin(); 220 iter != profile_notifications_.end(); iter++) { 221 if ((*iter).second->notification().origin_url() == source && 222 profile == (*iter).second->profile()) { 223 notification_ids.insert(iter->first); 224 } 225 } 226 return notification_ids; 227} 228 229bool MessageCenterNotificationManager::CancelAllBySourceOrigin( 230 const GURL& source) { 231 // Same pattern as CancelById, but more complicated than the above 232 // because there may be multiple notifications from the same source. 233 bool removed = false; 234 235 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 236 loopiter != profile_notifications_.end(); ) { 237 NotificationMap::iterator curiter = loopiter++; 238 if ((*curiter).second->notification().origin_url() == source) { 239 const std::string id = curiter->first; 240 RemoveProfileNotification(curiter->second); 241 message_center_->RemoveNotification(id, /* by_user */ false); 242 removed = true; 243 } 244 } 245 return removed; 246} 247 248bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) { 249 // Same pattern as CancelAllBySourceOrigin. 250 bool removed = false; 251 252 for (NotificationMap::iterator loopiter = profile_notifications_.begin(); 253 loopiter != profile_notifications_.end(); ) { 254 NotificationMap::iterator curiter = loopiter++; 255 if (profile == (*curiter).second->profile()) { 256 const std::string id = curiter->first; 257 RemoveProfileNotification(curiter->second); 258 message_center_->RemoveNotification(id, /* by_user */ false); 259 removed = true; 260 } 261 } 262 return removed; 263} 264 265void MessageCenterNotificationManager::CancelAll() { 266 message_center_->RemoveAllNotifications(/* by_user */ false); 267} 268 269//////////////////////////////////////////////////////////////////////////////// 270// MessageCenter::Observer 271void MessageCenterNotificationManager::OnNotificationRemoved( 272 const std::string& notification_id, 273 bool by_user) { 274 NotificationMap::const_iterator iter = 275 profile_notifications_.find(notification_id); 276 if (iter != profile_notifications_.end()) 277 RemoveProfileNotification(iter->second); 278 279#if defined(OS_WIN) 280 CheckFirstRunTimer(); 281#endif 282} 283 284void MessageCenterNotificationManager::OnCenterVisibilityChanged( 285 message_center::Visibility visibility) { 286#if defined(OS_WIN) 287 if (visibility == message_center::VISIBILITY_TRANSIENT) 288 CheckFirstRunTimer(); 289#endif 290} 291 292void MessageCenterNotificationManager::OnNotificationUpdated( 293 const std::string& notification_id) { 294#if defined(OS_WIN) 295 CheckFirstRunTimer(); 296#endif 297} 298 299void MessageCenterNotificationManager::EnsureMessageCenterClosed() { 300 if (tray_.get()) 301 tray_->GetMessageCenterTray()->HideMessageCenterBubble(); 302 303#if defined(USE_ASH) 304 if (ash::Shell::HasInstance()) { 305 ash::WebNotificationTray* tray = 306 ash::Shell::GetInstance()->GetWebNotificationTray(); 307 if (tray) 308 tray->GetMessageCenterTray()->HideMessageCenterBubble(); 309 } 310#endif 311} 312 313void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest( 314 message_center::MessageCenterTrayDelegate* delegate) { 315 tray_.reset(delegate); 316} 317 318//////////////////////////////////////////////////////////////////////////////// 319// ImageDownloads 320 321MessageCenterNotificationManager::ImageDownloads::ImageDownloads( 322 message_center::MessageCenter* message_center, 323 ImageDownloadsObserver* observer) 324 : message_center_(message_center), 325 pending_downloads_(0), 326 observer_(observer) { 327} 328 329MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { } 330 331void MessageCenterNotificationManager::ImageDownloads::StartDownloads( 332 const Notification& notification) { 333 // In case all downloads are synchronous, assume a pending download. 334 AddPendingDownload(); 335 336 // Notification primary icon. 337 StartDownloadWithImage( 338 notification, 339 ¬ification.icon(), 340 notification.icon_url(), 341 base::Bind(&message_center::MessageCenter::SetNotificationIcon, 342 base::Unretained(message_center_), 343 notification.delegate_id())); 344 345 // Notification image. 346 StartDownloadWithImage( 347 notification, 348 NULL, 349 notification.image_url(), 350 base::Bind(&message_center::MessageCenter::SetNotificationImage, 351 base::Unretained(message_center_), 352 notification.delegate_id())); 353 354 // Notification button icons. 355 StartDownloadWithImage( 356 notification, 357 NULL, 358 notification.button_one_icon_url(), 359 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 360 base::Unretained(message_center_), 361 notification.delegate_id(), 362 0)); 363 StartDownloadWithImage( 364 notification, 365 NULL, 366 notification.button_two_icon_url(), 367 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon, 368 base::Unretained(message_center_), 369 notification.delegate_id(), 370 1)); 371 372 // This should tell the observer we're done if everything was synchronous. 373 PendingDownloadCompleted(); 374} 375 376void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage( 377 const Notification& notification, 378 const gfx::Image* image, 379 const GURL& url, 380 const SetImageCallback& callback) { 381 // Set the image directly if we have it. 382 if (image && !image->IsEmpty()) { 383 callback.Run(*image); 384 return; 385 } 386 387 // Leave the image null if there's no URL. 388 if (url.is_empty()) 389 return; 390 391 content::WebContents* contents = notification.delegate()->GetWebContents(); 392 if (!contents) { 393 LOG(WARNING) << "Notification needs an image but has no WebContents"; 394 return; 395 } 396 397 AddPendingDownload(); 398 399 contents->DownloadImage( 400 url, 401 false, // Not a favicon 402 0, // No maximum size 403 base::Bind( 404 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete, 405 AsWeakPtr(), 406 callback)); 407} 408 409void MessageCenterNotificationManager::ImageDownloads::DownloadComplete( 410 const SetImageCallback& callback, 411 int download_id, 412 int http_status_code, 413 const GURL& image_url, 414 const std::vector<SkBitmap>& bitmaps, 415 const std::vector<gfx::Size>& original_bitmap_sizes) { 416 PendingDownloadCompleted(); 417 418 if (bitmaps.empty()) 419 return; 420 gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]); 421 callback.Run(image); 422} 423 424// Private methods. 425 426void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() { 427 ++pending_downloads_; 428} 429 430void 431MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() { 432 DCHECK(pending_downloads_ > 0); 433 if (--pending_downloads_ == 0 && observer_) 434 observer_->OnDownloadsCompleted(); 435} 436 437//////////////////////////////////////////////////////////////////////////////// 438// ProfileNotification 439 440MessageCenterNotificationManager::ProfileNotification::ProfileNotification( 441 Profile* profile, 442 const Notification& notification, 443 message_center::MessageCenter* message_center) 444 : profile_(profile), 445 notification_(notification), 446 downloads_(new ImageDownloads(message_center, this)) { 447 DCHECK(profile); 448#if defined(OS_CHROMEOS) 449 notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile)); 450#endif 451} 452 453MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() { 454} 455 456void MessageCenterNotificationManager::ProfileNotification::StartDownloads() { 457 downloads_->StartDownloads(notification_); 458} 459 460void 461MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() { 462 notification_.delegate()->ReleaseRenderViewHost(); 463} 464 465void 466MessageCenterNotificationManager::ProfileNotification::AddToAlternateProvider( 467 const std::string extension_id) { 468 // Convert data from Notification type to NotificationOptions type. 469 extensions::api::notifications::NotificationOptions options; 470 NotificationConversionHelper::NotificationToNotificationOptions(notification_, 471 &options); 472 473 // Send the notification to the alternate provider extension/app. 474 extensions::NotificationProviderEventRouter event_router(profile_); 475 event_router.CreateNotification(extension_id, 476 notification_.notifier_id().id, 477 notification_.delegate_id(), 478 options); 479} 480 481//////////////////////////////////////////////////////////////////////////////// 482// private 483 484void MessageCenterNotificationManager::AddProfileNotification( 485 ProfileNotification* profile_notification) { 486 std::string id = profile_notification->notification().delegate_id(); 487 // Notification ids should be unique. 488 DCHECK(profile_notifications_.find(id) == profile_notifications_.end()); 489 profile_notifications_[id] = profile_notification; 490} 491 492void MessageCenterNotificationManager::RemoveProfileNotification( 493 ProfileNotification* profile_notification) { 494 std::string id = profile_notification->notification().delegate_id(); 495 profile_notifications_.erase(id); 496 delete profile_notification; 497} 498 499MessageCenterNotificationManager::ProfileNotification* 500 MessageCenterNotificationManager::FindProfileNotification( 501 const std::string& id) const { 502 NotificationMap::const_iterator iter = profile_notifications_.find(id); 503 if (iter == profile_notifications_.end()) 504 return NULL; 505 506 return (*iter).second; 507} 508 509std::string 510MessageCenterNotificationManager::GetExtensionTakingOverNotifications( 511 Profile* profile) { 512 // TODO(liyanhou): When additional settings in Chrome Settings is implemented, 513 // change choosing the last app with permission to a user selected app. 514 extensions::ExtensionRegistry* registry = 515 extensions::ExtensionRegistry::Get(profile); 516 DCHECK(registry); 517 std::string extension_id; 518 for (extensions::ExtensionSet::const_iterator it = 519 registry->enabled_extensions().begin(); 520 it != registry->enabled_extensions().end(); 521 ++it) { 522 if ((*it->get()).permissions_data()->HasAPIPermission( 523 extensions::APIPermission::ID::kNotificationProvider)) { 524 extension_id = (*it->get()).id(); 525 return extension_id; 526 } 527 } 528 return extension_id; 529} 530