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(USE_AURA) && !defined(USE_ASH))
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_->RemoveObserver(this);
95 void MessageCenterNotificationManager::RegisterPrefs(
96 PrefRegistrySimple* registry) {
97 registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon,
99 registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true);
100 registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false);
103 ////////////////////////////////////////////////////////////////////////////////
104 // NotificationUIManager
106 void MessageCenterNotificationManager::Add(const Notification& notification,
108 if (Update(notification, profile))
111 DesktopNotificationServiceFactory::GetForProfile(profile)->
112 ShowWelcomeNotificationIfNecessary(notification);
114 AddProfileNotification(
115 new ProfileNotification(profile, notification, message_center_));
118 bool MessageCenterNotificationManager::Update(const Notification& notification,
120 const base::string16& replace_id = notification.replace_id();
121 if (replace_id.empty())
124 const GURL origin_url = notification.origin_url();
125 DCHECK(origin_url.is_valid());
127 // Since replace_id is provided by arbitrary JS, we need to use origin_url
128 // (which is an app url in case of app/extension) to scope the replace ids
129 // in the given profile.
130 for (NotificationMap::iterator iter = profile_notifications_.begin();
131 iter != profile_notifications_.end(); ++iter) {
132 ProfileNotification* old_notification = (*iter).second;
133 if (old_notification->notification().replace_id() == replace_id &&
134 old_notification->notification().origin_url() == origin_url &&
135 old_notification->profile() == profile) {
136 // Changing the type from non-progress to progress does not count towards
137 // the immediate update allowed in the message center.
139 old_notification->notification().notification_id();
140 DCHECK(message_center_->HasNotification(old_id));
142 // Add/remove notification in the local list but just update the same
143 // one in MessageCenter.
144 delete old_notification;
145 profile_notifications_.erase(old_id);
146 ProfileNotification* new_notification =
147 new ProfileNotification(profile, notification, message_center_);
148 profile_notifications_[notification.notification_id()] = new_notification;
150 // Now pass a copy to message center.
151 scoped_ptr<message_center::Notification> message_center_notification(
152 make_scoped_ptr(new message_center::Notification(notification)));
153 message_center_->UpdateNotification(old_id,
154 message_center_notification.Pass());
156 new_notification->StartDownloads();
163 const Notification* MessageCenterNotificationManager::FindById(
164 const std::string& id) const {
165 NotificationMap::const_iterator iter = profile_notifications_.find(id);
166 if (iter == profile_notifications_.end())
168 return &(iter->second->notification());
171 bool MessageCenterNotificationManager::CancelById(const std::string& id) {
172 // See if this ID hasn't been shown yet.
173 // If it has been shown, remove it.
174 NotificationMap::iterator iter = profile_notifications_.find(id);
175 if (iter == profile_notifications_.end())
178 RemoveProfileNotification(iter->second);
179 message_center_->RemoveNotification(id, /* by_user */ false);
183 std::set<std::string>
184 MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
186 const GURL& source) {
188 std::set<std::string> notification_ids;
189 for (NotificationMap::iterator iter = profile_notifications_.begin();
190 iter != profile_notifications_.end(); iter++) {
191 if ((*iter).second->notification().origin_url() == source &&
192 profile == (*iter).second->profile()) {
193 notification_ids.insert(iter->first);
196 return notification_ids;
199 bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
200 const GURL& source) {
201 // Same pattern as CancelById, but more complicated than the above
202 // because there may be multiple notifications from the same source.
203 bool removed = false;
205 for (NotificationMap::iterator loopiter = profile_notifications_.begin();
206 loopiter != profile_notifications_.end(); ) {
207 NotificationMap::iterator curiter = loopiter++;
208 if ((*curiter).second->notification().origin_url() == source) {
209 const std::string id = curiter->first;
210 RemoveProfileNotification(curiter->second);
211 message_center_->RemoveNotification(id, /* by_user */ false);
218 bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
219 // Same pattern as CancelAllBySourceOrigin.
220 bool removed = false;
222 for (NotificationMap::iterator loopiter = profile_notifications_.begin();
223 loopiter != profile_notifications_.end(); ) {
224 NotificationMap::iterator curiter = loopiter++;
225 if (profile == (*curiter).second->profile()) {
226 const std::string id = curiter->first;
227 RemoveProfileNotification(curiter->second);
228 message_center_->RemoveNotification(id, /* by_user */ false);
235 void MessageCenterNotificationManager::CancelAll() {
236 message_center_->RemoveAllNotifications(/* by_user */ false);
239 ////////////////////////////////////////////////////////////////////////////////
240 // MessageCenter::Observer
241 void MessageCenterNotificationManager::OnNotificationRemoved(
242 const std::string& notification_id,
244 NotificationMap::const_iterator iter =
245 profile_notifications_.find(notification_id);
246 if (iter != profile_notifications_.end())
247 RemoveProfileNotification(iter->second);
250 CheckFirstRunTimer();
254 void MessageCenterNotificationManager::OnCenterVisibilityChanged(
255 message_center::Visibility visibility) {
257 if (visibility == message_center::VISIBILITY_TRANSIENT)
258 CheckFirstRunTimer();
262 void MessageCenterNotificationManager::OnNotificationUpdated(
263 const std::string& notification_id) {
265 CheckFirstRunTimer();
269 void MessageCenterNotificationManager::EnsureMessageCenterClosed() {
271 tray_->GetMessageCenterTray()->HideMessageCenterBubble();
274 if (ash::Shell::HasInstance()) {
275 ash::WebNotificationTray* tray =
276 ash::Shell::GetInstance()->GetWebNotificationTray();
278 tray->GetMessageCenterTray()->HideMessageCenterBubble();
283 void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
284 message_center::MessageCenterTrayDelegate* delegate) {
285 tray_.reset(delegate);
288 void MessageCenterNotificationManager::Observe(
290 const content::NotificationSource& source,
291 const content::NotificationDetails& details) {
292 if (type == chrome::NOTIFICATION_FULLSCREEN_CHANGED) {
293 const bool is_fullscreen = *content::Details<bool>(details).ptr();
295 if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray())
296 tray_->GetMessageCenterTray()->HidePopupBubble();
300 ////////////////////////////////////////////////////////////////////////////////
303 MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
304 message_center::MessageCenter* message_center,
305 ImageDownloadsObserver* observer)
306 : message_center_(message_center),
307 pending_downloads_(0),
308 observer_(observer) {
311 MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
313 void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
314 const Notification& notification) {
315 // In case all downloads are synchronous, assume a pending download.
316 AddPendingDownload();
318 // Notification primary icon.
319 StartDownloadWithImage(
321 ¬ification.icon(),
322 notification.icon_url(),
323 base::Bind(&message_center::MessageCenter::SetNotificationIcon,
324 base::Unretained(message_center_),
325 notification.notification_id()));
327 // Notification image.
328 StartDownloadWithImage(
331 notification.image_url(),
332 base::Bind(&message_center::MessageCenter::SetNotificationImage,
333 base::Unretained(message_center_),
334 notification.notification_id()));
336 // Notification button icons.
337 StartDownloadWithImage(
340 notification.button_one_icon_url(),
341 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
342 base::Unretained(message_center_),
343 notification.notification_id(),
345 StartDownloadWithImage(
348 notification.button_two_icon_url(),
349 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
350 base::Unretained(message_center_),
351 notification.notification_id(),
354 // This should tell the observer we're done if everything was synchronous.
355 PendingDownloadCompleted();
358 void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
359 const Notification& notification,
360 const gfx::Image* image,
362 const SetImageCallback& callback) {
363 // Set the image directly if we have it.
364 if (image && !image->IsEmpty()) {
365 callback.Run(*image);
369 // Leave the image null if there's no URL.
373 content::WebContents* contents = notification.GetWebContents();
375 LOG(WARNING) << "Notification needs an image but has no WebContents";
379 AddPendingDownload();
381 contents->DownloadImage(
383 false, // Not a favicon
384 0, // No maximum size
386 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
391 void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
392 const SetImageCallback& callback,
394 int http_status_code,
395 const GURL& image_url,
396 const std::vector<SkBitmap>& bitmaps,
397 const std::vector<gfx::Size>& original_bitmap_sizes) {
398 PendingDownloadCompleted();
402 gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
408 void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
409 ++pending_downloads_;
413 MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
414 DCHECK(pending_downloads_ > 0);
415 if (--pending_downloads_ == 0 && observer_)
416 observer_->OnDownloadsCompleted();
419 ////////////////////////////////////////////////////////////////////////////////
420 // ProfileNotification
422 MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
424 const Notification& notification,
425 message_center::MessageCenter* message_center)
427 notification_(notification),
428 downloads_(new ImageDownloads(message_center, this)) {
430 #if defined(OS_CHROMEOS)
431 notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile));
435 MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
438 void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
439 downloads_->StartDownloads(notification_);
443 MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
444 notification_.DoneRendering();
448 MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
449 extensions::InfoMap* extension_info_map =
450 extensions::ExtensionSystem::Get(profile())->info_map();
451 extensions::ExtensionSet extensions;
452 extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
453 notification().origin_url(), notification().process_id(),
454 extensions::APIPermission::kNotification, &extensions);
456 DesktopNotificationService* desktop_service =
457 DesktopNotificationServiceFactory::GetForProfile(profile());
458 for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
459 iter != extensions.end(); ++iter) {
460 if (desktop_service->IsNotifierEnabled(message_center::NotifierId(
461 message_center::NotifierId::APPLICATION, (*iter)->id()))) {
462 return (*iter)->id();
465 return std::string();
468 ////////////////////////////////////////////////////////////////////////////////
471 void MessageCenterNotificationManager::AddProfileNotification(
472 ProfileNotification* profile_notification) {
473 const Notification& notification = profile_notification->notification();
474 std::string id = notification.notification_id();
475 // Notification ids should be unique.
476 DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
477 profile_notifications_[id] = profile_notification;
479 // Create the copy for message center, and ensure the extension ID is correct.
480 scoped_ptr<message_center::Notification> message_center_notification(
481 new message_center::Notification(notification));
482 message_center_->AddNotification(message_center_notification.Pass());
484 profile_notification->StartDownloads();
487 void MessageCenterNotificationManager::RemoveProfileNotification(
488 ProfileNotification* profile_notification) {
489 std::string id = profile_notification->notification().notification_id();
490 profile_notifications_.erase(id);
491 delete profile_notification;
494 MessageCenterNotificationManager::ProfileNotification*
495 MessageCenterNotificationManager::FindProfileNotification(
496 const std::string& id) const {
497 NotificationMap::const_iterator iter = profile_notifications_.find(id);
498 if (iter == profile_notifications_.end())
501 return (*iter).second;