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 "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"
34
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"
38 #endif
39
40 #if defined(USE_ASH)
41 #include "ash/shell.h"
42 #include "ash/system/web_notification/web_notification_tray.h"
43 #endif
44
45 #if defined(OS_WIN)
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;
49 #endif
50
MessageCenterNotificationManager(message_center::MessageCenter * message_center,PrefService * local_state,scoped_ptr<message_center::NotifierSettingsProvider> settings_provider)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),
56 #if defined(OS_WIN)
57 first_run_idle_timeout_(
58 base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)),
59 weak_factory_(this),
60 #endif
61 settings_provider_(settings_provider.Pass()),
62 system_observer_(this),
63 stats_collector_(message_center),
64 google_now_stats_collector_(message_center) {
65 #if defined(OS_WIN)
66 first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state);
67 #endif
68
69 message_center_->AddObserver(this);
70 message_center_->SetNotifierSettingsProvider(settings_provider_.get());
71
72 #if defined(OS_CHROMEOS)
73 blockers_.push_back(
74 new LoginStateNotificationBlockerChromeOS(message_center));
75 #else
76 blockers_.push_back(new ScreenLockNotificationBlocker(message_center));
77 #endif
78 blockers_.push_back(new FullscreenNotificationBlocker(message_center));
79
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());
85 #endif
86 registrar_.Add(this,
87 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
88 content::NotificationService::AllSources());
89 }
90
~MessageCenterNotificationManager()91 MessageCenterNotificationManager::~MessageCenterNotificationManager() {
92 message_center_->SetNotifierSettingsProvider(NULL);
93 message_center_->RemoveObserver(this);
94 }
95
RegisterPrefs(PrefRegistrySimple * registry)96 void MessageCenterNotificationManager::RegisterPrefs(
97 PrefRegistrySimple* registry) {
98 registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon,
99 false);
100 registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true);
101 registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false);
102 }
103
104 ////////////////////////////////////////////////////////////////////////////////
105 // NotificationUIManager
106
Add(const Notification & notification,Profile * profile)107 void MessageCenterNotificationManager::Add(const Notification& notification,
108 Profile* profile) {
109 if (Update(notification, profile))
110 return;
111
112 DesktopNotificationServiceFactory::GetForProfile(profile)->
113 ShowWelcomeNotificationIfNecessary(notification);
114
115 AddProfileNotification(
116 new ProfileNotification(profile, notification, message_center_));
117 }
118
Update(const Notification & notification,Profile * profile)119 bool MessageCenterNotificationManager::Update(const Notification& notification,
120 Profile* profile) {
121 const base::string16& replace_id = notification.replace_id();
122 if (replace_id.empty())
123 return false;
124
125 const GURL origin_url = notification.origin_url();
126 DCHECK(origin_url.is_valid());
127
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.
139 std::string old_id =
140 old_notification->notification().delegate_id();
141 DCHECK(message_center_->FindVisibleNotificationById(old_id));
142
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;
150
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());
156
157 new_notification->StartDownloads();
158 return true;
159 }
160 }
161 return false;
162 }
163
FindById(const std::string & id) const164 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())
168 return NULL;
169 return &(iter->second->notification());
170 }
171
CancelById(const std::string & id)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())
177 return false;
178
179 RemoveProfileNotification(iter->second);
180 message_center_->RemoveNotification(id, /* by_user */ false);
181 return true;
182 }
183
184 std::set<std::string>
GetAllIdsByProfileAndSourceOrigin(Profile * profile,const GURL & source)185 MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
186 Profile* profile,
187 const GURL& source) {
188
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);
195 }
196 }
197 return notification_ids;
198 }
199
CancelAllBySourceOrigin(const GURL & source)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;
205
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);
213 removed = true;
214 }
215 }
216 return removed;
217 }
218
CancelAllByProfile(Profile * profile)219 bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
220 // Same pattern as CancelAllBySourceOrigin.
221 bool removed = false;
222
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);
230 removed = true;
231 }
232 }
233 return removed;
234 }
235
CancelAll()236 void MessageCenterNotificationManager::CancelAll() {
237 message_center_->RemoveAllNotifications(/* by_user */ false);
238 }
239
240 ////////////////////////////////////////////////////////////////////////////////
241 // MessageCenter::Observer
OnNotificationRemoved(const std::string & notification_id,bool by_user)242 void MessageCenterNotificationManager::OnNotificationRemoved(
243 const std::string& notification_id,
244 bool by_user) {
245 NotificationMap::const_iterator iter =
246 profile_notifications_.find(notification_id);
247 if (iter != profile_notifications_.end())
248 RemoveProfileNotification(iter->second);
249
250 #if defined(OS_WIN)
251 CheckFirstRunTimer();
252 #endif
253 }
254
OnCenterVisibilityChanged(message_center::Visibility visibility)255 void MessageCenterNotificationManager::OnCenterVisibilityChanged(
256 message_center::Visibility visibility) {
257 #if defined(OS_WIN)
258 if (visibility == message_center::VISIBILITY_TRANSIENT)
259 CheckFirstRunTimer();
260 #endif
261 }
262
OnNotificationUpdated(const std::string & notification_id)263 void MessageCenterNotificationManager::OnNotificationUpdated(
264 const std::string& notification_id) {
265 #if defined(OS_WIN)
266 CheckFirstRunTimer();
267 #endif
268 }
269
EnsureMessageCenterClosed()270 void MessageCenterNotificationManager::EnsureMessageCenterClosed() {
271 if (tray_.get())
272 tray_->GetMessageCenterTray()->HideMessageCenterBubble();
273
274 #if defined(USE_ASH)
275 if (ash::Shell::HasInstance()) {
276 ash::WebNotificationTray* tray =
277 ash::Shell::GetInstance()->GetWebNotificationTray();
278 if (tray)
279 tray->GetMessageCenterTray()->HideMessageCenterBubble();
280 }
281 #endif
282 }
283
SetMessageCenterTrayDelegateForTest(message_center::MessageCenterTrayDelegate * delegate)284 void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
285 message_center::MessageCenterTrayDelegate* delegate) {
286 tray_.reset(delegate);
287 }
288
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)289 void MessageCenterNotificationManager::Observe(
290 int type,
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();
295
296 if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray())
297 tray_->GetMessageCenterTray()->HidePopupBubble();
298 }
299 }
300
301 ////////////////////////////////////////////////////////////////////////////////
302 // ImageDownloads
303
ImageDownloads(message_center::MessageCenter * message_center,ImageDownloadsObserver * observer)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) {
310 }
311
~ImageDownloads()312 MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
313
StartDownloads(const Notification & notification)314 void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
315 const Notification& notification) {
316 // In case all downloads are synchronous, assume a pending download.
317 AddPendingDownload();
318
319 // Notification primary icon.
320 StartDownloadWithImage(
321 notification,
322 ¬ification.icon(),
323 notification.icon_url(),
324 base::Bind(&message_center::MessageCenter::SetNotificationIcon,
325 base::Unretained(message_center_),
326 notification.delegate_id()));
327
328 // Notification image.
329 StartDownloadWithImage(
330 notification,
331 NULL,
332 notification.image_url(),
333 base::Bind(&message_center::MessageCenter::SetNotificationImage,
334 base::Unretained(message_center_),
335 notification.delegate_id()));
336
337 // Notification button icons.
338 StartDownloadWithImage(
339 notification,
340 NULL,
341 notification.button_one_icon_url(),
342 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
343 base::Unretained(message_center_),
344 notification.delegate_id(),
345 0));
346 StartDownloadWithImage(
347 notification,
348 NULL,
349 notification.button_two_icon_url(),
350 base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
351 base::Unretained(message_center_),
352 notification.delegate_id(),
353 1));
354
355 // This should tell the observer we're done if everything was synchronous.
356 PendingDownloadCompleted();
357 }
358
StartDownloadWithImage(const Notification & notification,const gfx::Image * image,const GURL & url,const SetImageCallback & callback)359 void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
360 const Notification& notification,
361 const gfx::Image* image,
362 const GURL& url,
363 const SetImageCallback& callback) {
364 // Set the image directly if we have it.
365 if (image && !image->IsEmpty()) {
366 callback.Run(*image);
367 return;
368 }
369
370 // Leave the image null if there's no URL.
371 if (url.is_empty())
372 return;
373
374 content::WebContents* contents = notification.GetWebContents();
375 if (!contents) {
376 LOG(WARNING) << "Notification needs an image but has no WebContents";
377 return;
378 }
379
380 AddPendingDownload();
381
382 contents->DownloadImage(
383 url,
384 false, // Not a favicon
385 0, // No maximum size
386 base::Bind(
387 &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
388 AsWeakPtr(),
389 callback));
390 }
391
DownloadComplete(const SetImageCallback & callback,int download_id,int http_status_code,const GURL & image_url,const std::vector<SkBitmap> & bitmaps,const std::vector<gfx::Size> & original_bitmap_sizes)392 void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
393 const SetImageCallback& callback,
394 int download_id,
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();
400
401 if (bitmaps.empty())
402 return;
403 gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
404 callback.Run(image);
405 }
406
407 // Private methods.
408
AddPendingDownload()409 void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
410 ++pending_downloads_;
411 }
412
413 void
PendingDownloadCompleted()414 MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
415 DCHECK(pending_downloads_ > 0);
416 if (--pending_downloads_ == 0 && observer_)
417 observer_->OnDownloadsCompleted();
418 }
419
420 ////////////////////////////////////////////////////////////////////////////////
421 // ProfileNotification
422
ProfileNotification(Profile * profile,const Notification & notification,message_center::MessageCenter * message_center)423 MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
424 Profile* profile,
425 const Notification& notification,
426 message_center::MessageCenter* message_center)
427 : profile_(profile),
428 notification_(notification),
429 downloads_(new ImageDownloads(message_center, this)) {
430 DCHECK(profile);
431 #if defined(OS_CHROMEOS)
432 notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile));
433 #endif
434 }
435
~ProfileNotification()436 MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
437 }
438
StartDownloads()439 void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
440 downloads_->StartDownloads(notification_);
441 }
442
443 void
OnDownloadsCompleted()444 MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
445 notification_.DoneRendering();
446 }
447
448 std::string
GetExtensionId()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);
456
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();
464 }
465 }
466 return std::string();
467 }
468
469 ////////////////////////////////////////////////////////////////////////////////
470 // private
471
AddProfileNotification(ProfileNotification * profile_notification)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;
479
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());
484
485 profile_notification->StartDownloads();
486 }
487
RemoveProfileNotification(ProfileNotification * profile_notification)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;
493 }
494
495 MessageCenterNotificationManager::ProfileNotification*
FindProfileNotification(const std::string & id) const496 MessageCenterNotificationManager::FindProfileNotification(
497 const std::string& id) const {
498 NotificationMap::const_iterator iter = profile_notifications_.find(id);
499 if (iter == profile_notifications_.end())
500 return NULL;
501
502 return (*iter).second;
503 }
504