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. 49 const int kFirstRunIdleDelaySeconds = 1; 50 #endif 51 52 MessageCenterNotificationManager::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 92 MessageCenterNotificationManager::~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 101 void 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 112 void 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 144 bool 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 193 const 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 201 bool 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 213 std::set<std::string> 214 MessageCenterNotificationManager::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 229 bool 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 248 bool 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 265 void MessageCenterNotificationManager::CancelAll() { 266 message_center_->RemoveAllNotifications(/* by_user */ false); 267 } 268 269 //////////////////////////////////////////////////////////////////////////////// 270 // MessageCenter::Observer 271 void 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 284 void 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 292 void MessageCenterNotificationManager::OnNotificationUpdated( 293 const std::string& notification_id) { 294 #if defined(OS_WIN) 295 CheckFirstRunTimer(); 296 #endif 297 } 298 299 void 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 313 void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest( 314 message_center::MessageCenterTrayDelegate* delegate) { 315 tray_.reset(delegate); 316 } 317 318 //////////////////////////////////////////////////////////////////////////////// 319 // ImageDownloads 320 321 MessageCenterNotificationManager::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 329 MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { } 330 331 void 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 376 void 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 409 void 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 426 void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() { 427 ++pending_downloads_; 428 } 429 430 void 431 MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() { 432 DCHECK(pending_downloads_ > 0); 433 if (--pending_downloads_ == 0 && observer_) 434 observer_->OnDownloadsCompleted(); 435 } 436 437 //////////////////////////////////////////////////////////////////////////////// 438 // ProfileNotification 439 440 MessageCenterNotificationManager::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 453 MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() { 454 } 455 456 void MessageCenterNotificationManager::ProfileNotification::StartDownloads() { 457 downloads_->StartDownloads(notification_); 458 } 459 460 void 461 MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() { 462 notification_.delegate()->ReleaseRenderViewHost(); 463 } 464 465 void 466 MessageCenterNotificationManager::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 484 void 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 492 void 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 499 MessageCenterNotificationManager::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 509 std::string 510 MessageCenterNotificationManager::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