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_service.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/notifications/desktop_notification_service.h"
12 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
13 #include "chrome/browser/notifications/fullscreen_notification_blocker.h"
14 #include "chrome/browser/notifications/message_center_settings_controller.h"
15 #include "chrome/browser/notifications/notification.h"
16 #include "chrome/browser/notifications/screen_lock_notification_blocker.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/browser_finder.h"
19 #include "chrome/browser/ui/chrome_pages.h"
20 #include "chrome/browser/ui/host_desktop.h"
21 #include "chrome/common/pref_names.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/common/url_constants.h"
25 #include "extensions/browser/extension_system.h"
26 #include "extensions/browser/info_map.h"
27 #include "extensions/common/extension_set.h"
28 #include "ui/gfx/image/image_skia.h"
29 #include "ui/message_center/message_center_style.h"
30 #include "ui/message_center/message_center_tray.h"
31 #include "ui/message_center/message_center_types.h"
32 #include "ui/message_center/notifier_settings.h"
34 #if defined(OS_CHROMEOS)
35 #include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h"
36 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
40 #include "ash/shell.h"
41 #include "ash/system/web_notification/web_notification_tray.h"
45 // The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all
46 // popups go away and the user has notifications in the message center.
47 const int kFirstRunIdleDelaySeconds
= 1;
50 MessageCenterNotificationManager::MessageCenterNotificationManager(
51 message_center::MessageCenter
* message_center
,
52 PrefService
* local_state
,
53 scoped_ptr
<message_center::NotifierSettingsProvider
> settings_provider
)
54 : message_center_(message_center
),
56 first_run_idle_timeout_(
57 base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds
)),
60 settings_provider_(settings_provider
.Pass()),
61 system_observer_(this),
62 stats_collector_(message_center
) {
64 first_run_pref_
.Init(prefs::kMessageCenterShowedFirstRunBalloon
, local_state
);
67 message_center_
->AddObserver(this);
68 message_center_
->SetNotifierSettingsProvider(settings_provider_
.get());
70 #if defined(OS_CHROMEOS)
72 new LoginStateNotificationBlockerChromeOS(message_center
));
74 blockers_
.push_back(new ScreenLockNotificationBlocker(message_center
));
76 blockers_
.push_back(new FullscreenNotificationBlocker(message_center
));
78 #if defined(OS_WIN) || defined(OS_MACOSX) \
79 || (defined(USE_AURA) && !defined(USE_ASH))
80 // On Windows, Linux and Mac, the notification manager owns the tray icon and
81 // views.Other platforms have global ownership and Create will return NULL.
82 tray_
.reset(message_center::CreateMessageCenterTray());
85 chrome::NOTIFICATION_FULLSCREEN_CHANGED
,
86 content::NotificationService::AllSources());
89 MessageCenterNotificationManager::~MessageCenterNotificationManager() {
90 message_center_
->RemoveObserver(this);
93 ////////////////////////////////////////////////////////////////////////////////
94 // NotificationUIManager
96 void MessageCenterNotificationManager::Add(const Notification
& notification
,
98 if (Update(notification
, profile
))
101 DesktopNotificationServiceFactory::GetForProfile(profile
)->
102 ShowWelcomeNotificationIfNecessary(notification
);
104 AddProfileNotification(
105 new ProfileNotification(profile
, notification
, message_center_
));
108 bool MessageCenterNotificationManager::Update(const Notification
& notification
,
110 const base::string16
& replace_id
= notification
.replace_id();
111 if (replace_id
.empty())
114 const GURL origin_url
= notification
.origin_url();
115 DCHECK(origin_url
.is_valid());
117 // Since replace_id is provided by arbitrary JS, we need to use origin_url
118 // (which is an app url in case of app/extension) to scope the replace ids
119 // in the given profile.
120 for (NotificationMap::iterator iter
= profile_notifications_
.begin();
121 iter
!= profile_notifications_
.end(); ++iter
) {
122 ProfileNotification
* old_notification
= (*iter
).second
;
123 if (old_notification
->notification().replace_id() == replace_id
&&
124 old_notification
->notification().origin_url() == origin_url
&&
125 old_notification
->profile() == profile
) {
126 // Changing the type from non-progress to progress does not count towards
127 // the immediate update allowed in the message center.
129 old_notification
->notification().notification_id();
130 DCHECK(message_center_
->HasNotification(old_id
));
132 // Add/remove notification in the local list but just update the same
133 // one in MessageCenter.
134 delete old_notification
;
135 profile_notifications_
.erase(old_id
);
136 ProfileNotification
* new_notification
=
137 new ProfileNotification(profile
, notification
, message_center_
);
138 profile_notifications_
[notification
.notification_id()] = new_notification
;
140 // Now pass a copy to message center.
141 scoped_ptr
<message_center::Notification
> message_center_notification(
142 make_scoped_ptr(new message_center::Notification(notification
)));
143 message_center_
->UpdateNotification(old_id
,
144 message_center_notification
.Pass());
146 new_notification
->StartDownloads();
153 const Notification
* MessageCenterNotificationManager::FindById(
154 const std::string
& id
) const {
155 NotificationMap::const_iterator iter
= profile_notifications_
.find(id
);
156 if (iter
== profile_notifications_
.end())
158 return &(iter
->second
->notification());
161 bool MessageCenterNotificationManager::CancelById(const std::string
& id
) {
162 // See if this ID hasn't been shown yet.
163 // If it has been shown, remove it.
164 NotificationMap::iterator iter
= profile_notifications_
.find(id
);
165 if (iter
== profile_notifications_
.end())
168 RemoveProfileNotification(iter
->second
);
169 message_center_
->RemoveNotification(id
, /* by_user */ false);
173 std::set
<std::string
>
174 MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
176 const GURL
& source
) {
178 std::set
<std::string
> notification_ids
;
179 for (NotificationMap::iterator iter
= profile_notifications_
.begin();
180 iter
!= profile_notifications_
.end(); iter
++) {
181 if ((*iter
).second
->notification().origin_url() == source
&&
182 profile
== (*iter
).second
->profile()) {
183 notification_ids
.insert(iter
->first
);
186 return notification_ids
;
189 bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
190 const GURL
& source
) {
191 // Same pattern as CancelById, but more complicated than the above
192 // because there may be multiple notifications from the same source.
193 bool removed
= false;
195 for (NotificationMap::iterator loopiter
= profile_notifications_
.begin();
196 loopiter
!= profile_notifications_
.end(); ) {
197 NotificationMap::iterator curiter
= loopiter
++;
198 if ((*curiter
).second
->notification().origin_url() == source
) {
199 const std::string id
= curiter
->first
;
200 RemoveProfileNotification(curiter
->second
);
201 message_center_
->RemoveNotification(id
, /* by_user */ false);
208 bool MessageCenterNotificationManager::CancelAllByProfile(Profile
* profile
) {
209 // Same pattern as CancelAllBySourceOrigin.
210 bool removed
= false;
212 for (NotificationMap::iterator loopiter
= profile_notifications_
.begin();
213 loopiter
!= profile_notifications_
.end(); ) {
214 NotificationMap::iterator curiter
= loopiter
++;
215 if (profile
== (*curiter
).second
->profile()) {
216 const std::string id
= curiter
->first
;
217 RemoveProfileNotification(curiter
->second
);
218 message_center_
->RemoveNotification(id
, /* by_user */ false);
225 void MessageCenterNotificationManager::CancelAll() {
226 message_center_
->RemoveAllNotifications(/* by_user */ false);
229 ////////////////////////////////////////////////////////////////////////////////
230 // MessageCenter::Observer
231 void MessageCenterNotificationManager::OnNotificationRemoved(
232 const std::string
& notification_id
,
234 NotificationMap::const_iterator iter
=
235 profile_notifications_
.find(notification_id
);
236 if (iter
!= profile_notifications_
.end())
237 RemoveProfileNotification(iter
->second
);
240 CheckFirstRunTimer();
244 void MessageCenterNotificationManager::OnCenterVisibilityChanged(
245 message_center::Visibility visibility
) {
247 if (visibility
== message_center::VISIBILITY_TRANSIENT
)
248 CheckFirstRunTimer();
252 void MessageCenterNotificationManager::OnNotificationUpdated(
253 const std::string
& notification_id
) {
255 CheckFirstRunTimer();
259 void MessageCenterNotificationManager::EnsureMessageCenterClosed() {
261 tray_
->GetMessageCenterTray()->HideMessageCenterBubble();
264 if (ash::Shell::HasInstance()) {
265 ash::WebNotificationTray
* tray
=
266 ash::Shell::GetInstance()->GetWebNotificationTray();
268 tray
->GetMessageCenterTray()->HideMessageCenterBubble();
273 void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
274 message_center::MessageCenterTrayDelegate
* delegate
) {
275 tray_
.reset(delegate
);
278 void MessageCenterNotificationManager::Observe(
280 const content::NotificationSource
& source
,
281 const content::NotificationDetails
& details
) {
282 if (type
== chrome::NOTIFICATION_FULLSCREEN_CHANGED
) {
283 const bool is_fullscreen
= *content::Details
<bool>(details
).ptr();
285 if (is_fullscreen
&& tray_
.get() && tray_
->GetMessageCenterTray())
286 tray_
->GetMessageCenterTray()->HidePopupBubble();
290 ////////////////////////////////////////////////////////////////////////////////
293 MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
294 message_center::MessageCenter
* message_center
,
295 ImageDownloadsObserver
* observer
)
296 : message_center_(message_center
),
297 pending_downloads_(0),
298 observer_(observer
) {
301 MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
303 void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
304 const Notification
& notification
) {
305 // In case all downloads are synchronous, assume a pending download.
306 AddPendingDownload();
308 // Notification primary icon.
309 StartDownloadWithImage(
311 ¬ification
.icon(),
312 notification
.icon_url(),
313 base::Bind(&message_center::MessageCenter::SetNotificationIcon
,
314 base::Unretained(message_center_
),
315 notification
.notification_id()));
317 // Notification image.
318 StartDownloadWithImage(
321 notification
.image_url(),
322 base::Bind(&message_center::MessageCenter::SetNotificationImage
,
323 base::Unretained(message_center_
),
324 notification
.notification_id()));
326 // Notification button icons.
327 StartDownloadWithImage(
330 notification
.button_one_icon_url(),
331 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon
,
332 base::Unretained(message_center_
),
333 notification
.notification_id(),
335 StartDownloadWithImage(
338 notification
.button_two_icon_url(),
339 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon
,
340 base::Unretained(message_center_
),
341 notification
.notification_id(),
344 // This should tell the observer we're done if everything was synchronous.
345 PendingDownloadCompleted();
348 void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
349 const Notification
& notification
,
350 const gfx::Image
* image
,
352 const SetImageCallback
& callback
) {
353 // Set the image directly if we have it.
354 if (image
&& !image
->IsEmpty()) {
355 callback
.Run(*image
);
359 // Leave the image null if there's no URL.
363 content::RenderViewHost
* host
= notification
.GetRenderViewHost();
365 LOG(WARNING
) << "Notification needs an image but has no RenderViewHost";
369 content::WebContents
* contents
=
370 content::WebContents::FromRenderViewHost(host
);
372 LOG(WARNING
) << "Notification needs an image but has no WebContents";
376 AddPendingDownload();
378 contents
->DownloadImage(
380 false, // Not a favicon
381 0, // No maximum size
383 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete
,
388 void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
389 const SetImageCallback
& callback
,
391 int http_status_code
,
392 const GURL
& image_url
,
393 const std::vector
<SkBitmap
>& bitmaps
,
394 const std::vector
<gfx::Size
>& original_bitmap_sizes
) {
395 PendingDownloadCompleted();
399 gfx::Image image
= gfx::Image::CreateFrom1xBitmap(bitmaps
[0]);
405 void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
406 ++pending_downloads_
;
410 MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
411 DCHECK(pending_downloads_
> 0);
412 if (--pending_downloads_
== 0 && observer_
)
413 observer_
->OnDownloadsCompleted();
416 ////////////////////////////////////////////////////////////////////////////////
417 // ProfileNotification
419 MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
421 const Notification
& notification
,
422 message_center::MessageCenter
* message_center
)
424 notification_(notification
),
425 downloads_(new ImageDownloads(message_center
, this)) {
427 #if defined(OS_CHROMEOS)
428 notification_
.set_profile_id(multi_user_util::GetUserIDFromProfile(profile
));
432 MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
435 void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
436 downloads_
->StartDownloads(notification_
);
440 MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
441 notification_
.DoneRendering();
445 MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
446 extensions::InfoMap
* extension_info_map
=
447 extensions::ExtensionSystem::Get(profile())->info_map();
448 extensions::ExtensionSet extensions
;
449 extension_info_map
->GetExtensionsWithAPIPermissionForSecurityOrigin(
450 notification().origin_url(), notification().process_id(),
451 extensions::APIPermission::kNotification
, &extensions
);
453 DesktopNotificationService
* desktop_service
=
454 DesktopNotificationServiceFactory::GetForProfile(profile());
455 for (extensions::ExtensionSet::const_iterator iter
= extensions
.begin();
456 iter
!= extensions
.end(); ++iter
) {
457 if (desktop_service
->IsNotifierEnabled(message_center::NotifierId(
458 message_center::NotifierId::APPLICATION
, (*iter
)->id()))) {
459 return (*iter
)->id();
462 return std::string();
465 ////////////////////////////////////////////////////////////////////////////////
468 void MessageCenterNotificationManager::AddProfileNotification(
469 ProfileNotification
* profile_notification
) {
470 const Notification
& notification
= profile_notification
->notification();
471 std::string id
= notification
.notification_id();
472 // Notification ids should be unique.
473 DCHECK(profile_notifications_
.find(id
) == profile_notifications_
.end());
474 profile_notifications_
[id
] = profile_notification
;
476 // Create the copy for message center, and ensure the extension ID is correct.
477 scoped_ptr
<message_center::Notification
> message_center_notification(
478 new message_center::Notification(notification
));
479 message_center_
->AddNotification(message_center_notification
.Pass());
481 profile_notification
->StartDownloads();
484 void MessageCenterNotificationManager::RemoveProfileNotification(
485 ProfileNotification
* profile_notification
) {
486 std::string id
= profile_notification
->notification().notification_id();
487 profile_notifications_
.erase(id
);
488 delete profile_notification
;
491 MessageCenterNotificationManager::ProfileNotification
*
492 MessageCenterNotificationManager::FindProfileNotification(
493 const std::string
& id
) const {
494 NotificationMap::const_iterator iter
= profile_notifications_
.find(id
);
495 if (iter
== profile_notifications_
.end())
498 return (*iter
).second
;