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.
5 #include "chrome/browser/notifications/message_center_notification_manager.h"
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 "chrome/browser/chrome_notification_types.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/fullscreen_notification_blocker.h"
15 #include "chrome/browser/notifications/message_center_settings_controller.h"
16 #include "chrome/browser/notifications/notification.h"
17 #include "chrome/browser/notifications/screen_lock_notification_blocker.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/browser_finder.h"
20 #include "chrome/browser/ui/chrome_pages.h"
21 #include "chrome/browser/ui/host_desktop.h"
22 #include "chrome/common/pref_names.h"
23 #include "content/public/browser/notification_service.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/common/url_constants.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/browser/info_map.h"
28 #include "extensions/common/extension_set.h"
29 #include "ui/gfx/image/image_skia.h"
30 #include "ui/message_center/message_center_style.h"
31 #include "ui/message_center/message_center_tray.h"
32 #include "ui/message_center/message_center_types.h"
33 #include "ui/message_center/notifier_settings.h"
35 #if defined(OS_CHROMEOS)
36 #include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h"
37 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
41 #include "ash/shell.h"
42 #include "ash/system/web_notification/web_notification_tray.h"
46 // The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all
47 // popups go away and the user has notifications in the message center.
48 const int kFirstRunIdleDelaySeconds = 1;
51 MessageCenterNotificationManager::MessageCenterNotificationManager(
52 message_center::MessageCenter* message_center,
53 PrefService* local_state,
54 scoped_ptr<message_center::NotifierSettingsProvider> settings_provider)
55 : message_center_(message_center),
57 first_run_idle_timeout_(
58 base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)),
61 settings_provider_(settings_provider.Pass()),
62 system_observer_(this),
63 stats_collector_(message_center),
64 google_now_stats_collector_(message_center) {
66 first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state);
69 message_center_->AddObserver(this);
70 message_center_->SetNotifierSettingsProvider(settings_provider_.get());
72 #if defined(OS_CHROMEOS)
74 new LoginStateNotificationBlockerChromeOS(message_center));
76 blockers_.push_back(new ScreenLockNotificationBlocker(message_center));
78 blockers_.push_back(new FullscreenNotificationBlocker(message_center));
80 #if defined(OS_WIN) || defined(OS_MACOSX) \
81 || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
82 // On Windows, Linux and Mac, the notification manager owns the tray icon and
83 // views.Other platforms have global ownership and Create will return NULL.
84 tray_.reset(message_center::CreateMessageCenterTray());
87 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
88 content::NotificationService::AllSources());
91 MessageCenterNotificationManager::~MessageCenterNotificationManager() {
92 message_center_->SetNotifierSettingsProvider(NULL);
93 message_center_->RemoveObserver(this);
96 void MessageCenterNotificationManager::RegisterPrefs(
97 PrefRegistrySimple* registry) {
98 registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon,
100 registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true);
101 registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false);
104 ////////////////////////////////////////////////////////////////////////////////
105 // NotificationUIManager
107 void MessageCenterNotificationManager::Add(const Notification& notification,
109 if (Update(notification, profile))
112 DesktopNotificationServiceFactory::GetForProfile(profile)->
113 ShowWelcomeNotificationIfNecessary(notification);
115 AddProfileNotification(
116 new ProfileNotification(profile, notification, message_center_));
119 bool MessageCenterNotificationManager::Update(const Notification& notification,
121 const base::string16& replace_id = notification.replace_id();
122 if (replace_id.empty())
125 const GURL origin_url = notification.origin_url();
126 DCHECK(origin_url.is_valid());
128 // Since replace_id is provided by arbitrary JS, we need to use origin_url
129 // (which is an app url in case of app/extension) to scope the replace ids
130 // in the given profile.
131 for (NotificationMap::iterator iter = profile_notifications_.begin();
132 iter != profile_notifications_.end(); ++iter) {
133 ProfileNotification* old_notification = (*iter).second;
134 if (old_notification->notification().replace_id() == replace_id &&
135 old_notification->notification().origin_url() == origin_url &&
136 old_notification->profile() == profile) {
137 // Changing the type from non-progress to progress does not count towards
138 // the immediate update allowed in the message center.
140 old_notification->notification().delegate_id();
141 DCHECK(message_center_->FindVisibleNotificationById(old_id));
143 // Add/remove notification in the local list but just update the same
144 // one in MessageCenter.
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.delegate_id()] = new_notification;
151 // Now pass a copy to message center.
152 scoped_ptr<message_center::Notification> message_center_notification(
153 make_scoped_ptr(new message_center::Notification(notification)));
154 message_center_->UpdateNotification(old_id,
155 message_center_notification.Pass());
157 new_notification->StartDownloads();
164 const Notification* MessageCenterNotificationManager::FindById(
165 const std::string& id) const {
166 NotificationMap::const_iterator iter = profile_notifications_.find(id);
167 if (iter == profile_notifications_.end())
169 return &(iter->second->notification());
172 bool MessageCenterNotificationManager::CancelById(const std::string& id) {
173 // See if this ID hasn't been shown yet.
174 // If it has been shown, remove it.
175 NotificationMap::iterator iter = profile_notifications_.find(id);
176 if (iter == profile_notifications_.end())
179 RemoveProfileNotification(iter->second);
180 message_center_->RemoveNotification(id, /* by_user */ false);
184 std::set<std::string>
185 MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
187 const GURL& source) {
189 std::set<std::string> notification_ids;
190 for (NotificationMap::iterator iter = profile_notifications_.begin();
191 iter != profile_notifications_.end(); iter++) {
192 if ((*iter).second->notification().origin_url() == source &&
193 profile == (*iter).second->profile()) {
194 notification_ids.insert(iter->first);
197 return notification_ids;
200 bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
201 const GURL& source) {
202 // Same pattern as CancelById, but more complicated than the above
203 // because there may be multiple notifications from the same source.
204 bool removed = false;
206 for (NotificationMap::iterator loopiter = profile_notifications_.begin();
207 loopiter != profile_notifications_.end(); ) {
208 NotificationMap::iterator curiter = loopiter++;
209 if ((*curiter).second->notification().origin_url() == source) {
210 const std::string id = curiter->first;
211 RemoveProfileNotification(curiter->second);
212 message_center_->RemoveNotification(id, /* by_user */ false);
219 bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
220 // Same pattern as CancelAllBySourceOrigin.
221 bool removed = false;
223 for (NotificationMap::iterator loopiter = profile_notifications_.begin();
224 loopiter != profile_notifications_.end(); ) {
225 NotificationMap::iterator curiter = loopiter++;
226 if (profile == (*curiter).second->profile()) {
227 const std::string id = curiter->first;
228 RemoveProfileNotification(curiter->second);
229 message_center_->RemoveNotification(id, /* by_user */ false);
236 void MessageCenterNotificationManager::CancelAll() {
237 message_center_->RemoveAllNotifications(/* by_user */ false);
240 ////////////////////////////////////////////////////////////////////////////////
241 // MessageCenter::Observer
242 void MessageCenterNotificationManager::OnNotificationRemoved(
243 const std::string& notification_id,
245 NotificationMap::const_iterator iter =
246 profile_notifications_.find(notification_id);
247 if (iter != profile_notifications_.end())
248 RemoveProfileNotification(iter->second);
251 CheckFirstRunTimer();
255 void MessageCenterNotificationManager::OnCenterVisibilityChanged(
256 message_center::Visibility visibility) {
258 if (visibility == message_center::VISIBILITY_TRANSIENT)
259 CheckFirstRunTimer();
263 void MessageCenterNotificationManager::OnNotificationUpdated(
264 const std::string& notification_id) {
266 CheckFirstRunTimer();
270 void MessageCenterNotificationManager::EnsureMessageCenterClosed() {
272 tray_->GetMessageCenterTray()->HideMessageCenterBubble();
275 if (ash::Shell::HasInstance()) {
276 ash::WebNotificationTray* tray =
277 ash::Shell::GetInstance()->GetWebNotificationTray();
279 tray->GetMessageCenterTray()->HideMessageCenterBubble();
284 void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
285 message_center::MessageCenterTrayDelegate* delegate) {
286 tray_.reset(delegate);
289 void MessageCenterNotificationManager::Observe(
291 const content::NotificationSource& source,
292 const content::NotificationDetails& details) {
293 if (type == chrome::NOTIFICATION_FULLSCREEN_CHANGED) {
294 const bool is_fullscreen = *content::Details<bool>(details).ptr();
296 if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray())
297 tray_->GetMessageCenterTray()->HidePopupBubble();
301 ////////////////////////////////////////////////////////////////////////////////
304 MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
305 message_center::MessageCenter* message_center,
306 ImageDownloadsObserver* observer)
307 : message_center_(message_center),
308 pending_downloads_(0),
309 observer_(observer) {
312 MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
314 void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
315 const Notification& notification) {
316 // In case all downloads are synchronous, assume a pending download.
317 AddPendingDownload();
319 // Notification primary icon.
320 StartDownloadWithImage(
322 ¬ification.icon(),
323 notification.icon_url(),
324 base::Bind(&message_center::MessageCenter::SetNotificationIcon,
325 base::Unretained(message_center_),
326 notification.delegate_id()));
328 // Notification image.
329 StartDownloadWithImage(
332 notification.image_url(),
333 base::Bind(&message_center::MessageCenter::SetNotificationImage,
334 base::Unretained(message_center_),
335 notification.delegate_id()));
337 // Notification button icons.
338 StartDownloadWithImage(
341 notification.button_one_icon_url(),
342 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
343 base::Unretained(message_center_),
344 notification.delegate_id(),
346 StartDownloadWithImage(
349 notification.button_two_icon_url(),
350 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
351 base::Unretained(message_center_),
352 notification.delegate_id(),
355 // This should tell the observer we're done if everything was synchronous.
356 PendingDownloadCompleted();
359 void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
360 const Notification& notification,
361 const gfx::Image* image,
363 const SetImageCallback& callback) {
364 // Set the image directly if we have it.
365 if (image && !image->IsEmpty()) {
366 callback.Run(*image);
370 // Leave the image null if there's no URL.
374 content::WebContents* contents = notification.GetWebContents();
376 LOG(WARNING) << "Notification needs an image but has no WebContents";
380 AddPendingDownload();
382 contents->DownloadImage(
384 false, // Not a favicon
385 0, // No maximum size
387 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
392 void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
393 const SetImageCallback& callback,
395 int http_status_code,
396 const GURL& image_url,
397 const std::vector<SkBitmap>& bitmaps,
398 const std::vector<gfx::Size>& original_bitmap_sizes) {
399 PendingDownloadCompleted();
403 gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
409 void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
410 ++pending_downloads_;
414 MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
415 DCHECK(pending_downloads_ > 0);
416 if (--pending_downloads_ == 0 && observer_)
417 observer_->OnDownloadsCompleted();
420 ////////////////////////////////////////////////////////////////////////////////
421 // ProfileNotification
423 MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
425 const Notification& notification,
426 message_center::MessageCenter* message_center)
428 notification_(notification),
429 downloads_(new ImageDownloads(message_center, this)) {
431 #if defined(OS_CHROMEOS)
432 notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile));
436 MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
439 void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
440 downloads_->StartDownloads(notification_);
444 MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
445 notification_.DoneRendering();
449 MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
450 extensions::InfoMap* extension_info_map =
451 extensions::ExtensionSystem::Get(profile())->info_map();
452 extensions::ExtensionSet extensions;
453 extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
454 notification().origin_url(), notification().process_id(),
455 extensions::APIPermission::kNotification, &extensions);
457 DesktopNotificationService* desktop_service =
458 DesktopNotificationServiceFactory::GetForProfile(profile());
459 for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
460 iter != extensions.end(); ++iter) {
461 if (desktop_service->IsNotifierEnabled(message_center::NotifierId(
462 message_center::NotifierId::APPLICATION, (*iter)->id()))) {
463 return (*iter)->id();
466 return std::string();
469 ////////////////////////////////////////////////////////////////////////////////
472 void MessageCenterNotificationManager::AddProfileNotification(
473 ProfileNotification* profile_notification) {
474 const Notification& notification = profile_notification->notification();
475 std::string id = notification.delegate_id();
476 // Notification ids should be unique.
477 DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
478 profile_notifications_[id] = profile_notification;
480 // Create the copy for message center, and ensure the extension ID is correct.
481 scoped_ptr<message_center::Notification> message_center_notification(
482 new message_center::Notification(notification));
483 message_center_->AddNotification(message_center_notification.Pass());
485 profile_notification->StartDownloads();
488 void MessageCenterNotificationManager::RemoveProfileNotification(
489 ProfileNotification* profile_notification) {
490 std::string id = profile_notification->notification().delegate_id();
491 profile_notifications_.erase(id);
492 delete profile_notification;
495 MessageCenterNotificationManager::ProfileNotification*
496 MessageCenterNotificationManager::FindProfileNotification(
497 const std::string& id) const {
498 NotificationMap::const_iterator iter = profile_notifications_.find(id);
499 if (iter == profile_notifications_.end())
502 return (*iter).second;