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/extensions/extension_system.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/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 // The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all
41 // popups go away and the user has notifications in the message center.
42 const int kFirstRunIdleDelaySeconds
= 1;
45 MessageCenterNotificationManager::MessageCenterNotificationManager(
46 message_center::MessageCenter
* message_center
,
47 PrefService
* local_state
,
48 scoped_ptr
<message_center::NotifierSettingsProvider
> settings_provider
)
49 : message_center_(message_center
),
51 first_run_idle_timeout_(
52 base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds
)),
55 settings_provider_(settings_provider
.Pass()),
56 system_observer_(this),
57 stats_collector_(message_center
) {
59 first_run_pref_
.Init(prefs::kMessageCenterShowedFirstRunBalloon
, local_state
);
62 message_center_
->AddObserver(this);
63 message_center_
->SetNotifierSettingsProvider(settings_provider_
.get());
65 #if defined(OS_CHROMEOS)
67 new LoginStateNotificationBlockerChromeOS(message_center
));
69 blockers_
.push_back(new ScreenLockNotificationBlocker(message_center
));
71 blockers_
.push_back(new FullscreenNotificationBlocker(message_center
));
73 #if defined(OS_WIN) || defined(OS_MACOSX) \
74 || (defined(USE_AURA) && !defined(USE_ASH))
75 // On Windows, Linux and Mac, the notification manager owns the tray icon and
76 // views.Other platforms have global ownership and Create will return NULL.
77 tray_
.reset(message_center::CreateMessageCenterTray());
80 chrome::NOTIFICATION_FULLSCREEN_CHANGED
,
81 content::NotificationService::AllSources());
84 MessageCenterNotificationManager::~MessageCenterNotificationManager() {
85 message_center_
->RemoveObserver(this);
88 ////////////////////////////////////////////////////////////////////////////////
89 // NotificationUIManager
91 void MessageCenterNotificationManager::Add(const Notification
& notification
,
93 if (Update(notification
, profile
))
96 DesktopNotificationServiceFactory::GetForProfile(profile
)->
97 ShowWelcomeNotificationIfNecessary(notification
);
99 AddProfileNotification(
100 new ProfileNotification(profile
, notification
, message_center_
));
103 bool MessageCenterNotificationManager::Update(const Notification
& notification
,
105 const base::string16
& replace_id
= notification
.replace_id();
106 if (replace_id
.empty())
109 const GURL origin_url
= notification
.origin_url();
110 DCHECK(origin_url
.is_valid());
112 // Since replace_id is provided by arbitrary JS, we need to use origin_url
113 // (which is an app url in case of app/extension) to scope the replace ids
114 // in the given profile.
115 for (NotificationMap::iterator iter
= profile_notifications_
.begin();
116 iter
!= profile_notifications_
.end(); ++iter
) {
117 ProfileNotification
* old_notification
= (*iter
).second
;
118 if (old_notification
->notification().replace_id() == replace_id
&&
119 old_notification
->notification().origin_url() == origin_url
&&
120 old_notification
->profile() == profile
) {
121 // Changing the type from non-progress to progress does not count towards
122 // the immediate update allowed in the message center.
124 old_notification
->notification().notification_id();
125 DCHECK(message_center_
->HasNotification(old_id
));
127 // Add/remove notification in the local list but just update the same
128 // one in MessageCenter.
129 delete old_notification
;
130 profile_notifications_
.erase(old_id
);
131 ProfileNotification
* new_notification
=
132 new ProfileNotification(profile
, notification
, message_center_
);
133 profile_notifications_
[notification
.notification_id()] = new_notification
;
135 // Now pass a copy to message center.
136 scoped_ptr
<message_center::Notification
> message_center_notification(
137 make_scoped_ptr(new message_center::Notification(notification
)));
138 message_center_
->UpdateNotification(old_id
,
139 message_center_notification
.Pass());
141 new_notification
->StartDownloads();
148 const Notification
* MessageCenterNotificationManager::FindById(
149 const std::string
& id
) const {
150 NotificationMap::const_iterator iter
= profile_notifications_
.find(id
);
151 if (iter
== profile_notifications_
.end())
153 return &(iter
->second
->notification());
156 bool MessageCenterNotificationManager::CancelById(const std::string
& id
) {
157 // See if this ID hasn't been shown yet.
158 // If it has been shown, remove it.
159 NotificationMap::iterator iter
= profile_notifications_
.find(id
);
160 if (iter
== profile_notifications_
.end())
163 RemoveProfileNotification(iter
->second
);
164 message_center_
->RemoveNotification(id
, /* by_user */ false);
168 std::set
<std::string
>
169 MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
171 const GURL
& source
) {
173 std::set
<std::string
> notification_ids
;
174 for (NotificationMap::iterator iter
= profile_notifications_
.begin();
175 iter
!= profile_notifications_
.end(); iter
++) {
176 if ((*iter
).second
->notification().origin_url() == source
&&
177 profile
== (*iter
).second
->profile()) {
178 notification_ids
.insert(iter
->first
);
181 return notification_ids
;
184 bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
185 const GURL
& source
) {
186 // Same pattern as CancelById, but more complicated than the above
187 // because there may be multiple notifications from the same source.
188 bool removed
= false;
190 for (NotificationMap::iterator loopiter
= profile_notifications_
.begin();
191 loopiter
!= profile_notifications_
.end(); ) {
192 NotificationMap::iterator curiter
= loopiter
++;
193 if ((*curiter
).second
->notification().origin_url() == source
) {
194 const std::string id
= curiter
->first
;
195 RemoveProfileNotification(curiter
->second
);
196 message_center_
->RemoveNotification(id
, /* by_user */ false);
203 bool MessageCenterNotificationManager::CancelAllByProfile(Profile
* profile
) {
204 // Same pattern as CancelAllBySourceOrigin.
205 bool removed
= false;
207 for (NotificationMap::iterator loopiter
= profile_notifications_
.begin();
208 loopiter
!= profile_notifications_
.end(); ) {
209 NotificationMap::iterator curiter
= loopiter
++;
210 if (profile
== (*curiter
).second
->profile()) {
211 const std::string id
= curiter
->first
;
212 RemoveProfileNotification(curiter
->second
);
213 message_center_
->RemoveNotification(id
, /* by_user */ false);
220 void MessageCenterNotificationManager::CancelAll() {
221 message_center_
->RemoveAllNotifications(/* by_user */ false);
224 ////////////////////////////////////////////////////////////////////////////////
225 // MessageCenter::Observer
226 void MessageCenterNotificationManager::OnNotificationRemoved(
227 const std::string
& notification_id
,
229 NotificationMap::const_iterator iter
=
230 profile_notifications_
.find(notification_id
);
231 if (iter
!= profile_notifications_
.end())
232 RemoveProfileNotification(iter
->second
);
235 CheckFirstRunTimer();
239 void MessageCenterNotificationManager::OnCenterVisibilityChanged(
240 message_center::Visibility visibility
) {
242 if (visibility
== message_center::VISIBILITY_TRANSIENT
)
243 CheckFirstRunTimer();
247 void MessageCenterNotificationManager::OnNotificationUpdated(
248 const std::string
& notification_id
) {
250 CheckFirstRunTimer();
254 void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
255 message_center::MessageCenterTrayDelegate
* delegate
) {
256 tray_
.reset(delegate
);
259 void MessageCenterNotificationManager::Observe(
261 const content::NotificationSource
& source
,
262 const content::NotificationDetails
& details
) {
263 if (type
== chrome::NOTIFICATION_FULLSCREEN_CHANGED
) {
264 const bool is_fullscreen
= *content::Details
<bool>(details
).ptr();
266 if (is_fullscreen
&& tray_
.get() && tray_
->GetMessageCenterTray())
267 tray_
->GetMessageCenterTray()->HidePopupBubble();
271 ////////////////////////////////////////////////////////////////////////////////
274 MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
275 message_center::MessageCenter
* message_center
,
276 ImageDownloadsObserver
* observer
)
277 : message_center_(message_center
),
278 pending_downloads_(0),
279 observer_(observer
) {
282 MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
284 void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
285 const Notification
& notification
) {
286 // In case all downloads are synchronous, assume a pending download.
287 AddPendingDownload();
289 // Notification primary icon.
290 StartDownloadWithImage(
292 ¬ification
.icon(),
293 notification
.icon_url(),
294 base::Bind(&message_center::MessageCenter::SetNotificationIcon
,
295 base::Unretained(message_center_
),
296 notification
.notification_id()));
298 // Notification image.
299 StartDownloadWithImage(
302 notification
.image_url(),
303 base::Bind(&message_center::MessageCenter::SetNotificationImage
,
304 base::Unretained(message_center_
),
305 notification
.notification_id()));
307 // Notification button icons.
308 StartDownloadWithImage(
311 notification
.button_one_icon_url(),
312 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon
,
313 base::Unretained(message_center_
),
314 notification
.notification_id(),
316 StartDownloadWithImage(
319 notification
.button_two_icon_url(),
320 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon
,
321 base::Unretained(message_center_
),
322 notification
.notification_id(),
325 // This should tell the observer we're done if everything was synchronous.
326 PendingDownloadCompleted();
329 void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
330 const Notification
& notification
,
331 const gfx::Image
* image
,
333 const SetImageCallback
& callback
) {
334 // Set the image directly if we have it.
335 if (image
&& !image
->IsEmpty()) {
336 callback
.Run(*image
);
340 // Leave the image null if there's no URL.
344 content::RenderViewHost
* host
= notification
.GetRenderViewHost();
346 LOG(WARNING
) << "Notification needs an image but has no RenderViewHost";
350 content::WebContents
* contents
=
351 content::WebContents::FromRenderViewHost(host
);
353 LOG(WARNING
) << "Notification needs an image but has no WebContents";
357 AddPendingDownload();
359 contents
->DownloadImage(
361 false, // Not a favicon
362 0, // No maximum size
364 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete
,
369 void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
370 const SetImageCallback
& callback
,
372 int http_status_code
,
373 const GURL
& image_url
,
374 const std::vector
<SkBitmap
>& bitmaps
,
375 const std::vector
<gfx::Size
>& original_bitmap_sizes
) {
376 PendingDownloadCompleted();
380 gfx::Image image
= gfx::Image::CreateFrom1xBitmap(bitmaps
[0]);
386 void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
387 ++pending_downloads_
;
391 MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
392 DCHECK(pending_downloads_
> 0);
393 if (--pending_downloads_
== 0 && observer_
)
394 observer_
->OnDownloadsCompleted();
397 ////////////////////////////////////////////////////////////////////////////////
398 // ProfileNotification
400 MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
402 const Notification
& notification
,
403 message_center::MessageCenter
* message_center
)
405 notification_(notification
),
406 downloads_(new ImageDownloads(message_center
, this)) {
408 #if defined(OS_CHROMEOS)
409 notification_
.set_profile_id(multi_user_util::GetUserIDFromProfile(profile
));
413 MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
416 void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
417 downloads_
->StartDownloads(notification_
);
421 MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
422 notification_
.DoneRendering();
426 MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
427 extensions::InfoMap
* extension_info_map
=
428 extensions::ExtensionSystem::Get(profile())->info_map();
429 extensions::ExtensionSet extensions
;
430 extension_info_map
->GetExtensionsWithAPIPermissionForSecurityOrigin(
431 notification().origin_url(), notification().process_id(),
432 extensions::APIPermission::kNotification
, &extensions
);
434 DesktopNotificationService
* desktop_service
=
435 DesktopNotificationServiceFactory::GetForProfile(profile());
436 for (extensions::ExtensionSet::const_iterator iter
= extensions
.begin();
437 iter
!= extensions
.end(); ++iter
) {
438 if (desktop_service
->IsNotifierEnabled(message_center::NotifierId(
439 message_center::NotifierId::APPLICATION
, (*iter
)->id()))) {
440 return (*iter
)->id();
443 return std::string();
446 ////////////////////////////////////////////////////////////////////////////////
449 void MessageCenterNotificationManager::AddProfileNotification(
450 ProfileNotification
* profile_notification
) {
451 const Notification
& notification
= profile_notification
->notification();
452 std::string id
= notification
.notification_id();
453 // Notification ids should be unique.
454 DCHECK(profile_notifications_
.find(id
) == profile_notifications_
.end());
455 profile_notifications_
[id
] = profile_notification
;
457 // Create the copy for message center, and ensure the extension ID is correct.
458 scoped_ptr
<message_center::Notification
> message_center_notification(
459 new message_center::Notification(notification
));
460 message_center_
->AddNotification(message_center_notification
.Pass());
462 profile_notification
->StartDownloads();
465 void MessageCenterNotificationManager::RemoveProfileNotification(
466 ProfileNotification
* profile_notification
) {
467 std::string id
= profile_notification
->notification().notification_id();
468 profile_notifications_
.erase(id
);
469 delete profile_notification
;
472 MessageCenterNotificationManager::ProfileNotification
*
473 MessageCenterNotificationManager::FindProfileNotification(
474 const std::string
& id
) const {
475 NotificationMap::const_iterator iter
= profile_notifications_
.find(id
);
476 if (iter
== profile_notifications_
.end())
479 return (*iter
).second
;