message_center_notification_manager.cc revision bb1529ce867d8845a77ec7cdf3e3003ef1771a40
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_service.h"
10#include "chrome/browser/chrome_notification_types.h"
11#include "chrome/browser/extensions/extension_info_map.h"
12#include "chrome/browser/extensions/extension_system.h"
13#include "chrome/browser/notifications/desktop_notification_service.h"
14#include "chrome/browser/notifications/desktop_notification_service_factory.h"
15#include "chrome/browser/notifications/message_center_settings_controller.h"
16#include "chrome/browser/notifications/notification.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/extensions/extension_set.h"
22#include "chrome/common/pref_names.h"
23#include "content/public/browser/notification_service.h"
24#include "content/public/browser/user_metrics.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/common/url_constants.h"
27#include "ui/message_center/message_center_style.h"
28#include "ui/message_center/message_center_tray.h"
29#include "ui/message_center/notifier_settings.h"
30
31namespace {
32// The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all
33// popups go away and the user has notifications in the message center.
34const int kFirstRunIdleDelaySeconds = 1;
35}  // namespace
36
37MessageCenterNotificationManager::MessageCenterNotificationManager(
38    message_center::MessageCenter* message_center,
39    PrefService* local_state,
40    scoped_ptr<message_center::NotifierSettingsProvider> settings_provider)
41    : message_center_(message_center),
42#if defined(OS_WIN)
43      first_run_idle_timeout_(
44          base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)),
45      weak_factory_(this),
46#endif
47      settings_provider_(settings_provider.Pass()) {
48#if defined(OS_WIN)
49  first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state);
50#endif
51
52  message_center_->SetDelegate(this);
53  message_center_->AddObserver(this);
54  message_center_->SetNotifierSettingsProvider(settings_provider_.get());
55
56#if defined(OS_WIN) || defined(OS_MACOSX) \
57  || (defined(USE_AURA) && !defined(USE_ASH))
58  // On Windows, Linux and Mac, the notification manager owns the tray icon and
59  // views.Other platforms have global ownership and Create will return NULL.
60  tray_.reset(message_center::CreateMessageCenterTray());
61#endif
62  registrar_.Add(this,
63                 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
64                 content::NotificationService::AllSources());
65}
66
67MessageCenterNotificationManager::~MessageCenterNotificationManager() {
68  message_center_->RemoveObserver(this);
69}
70
71////////////////////////////////////////////////////////////////////////////////
72// NotificationUIManager
73
74const Notification* MessageCenterNotificationManager::FindById(
75    const std::string& id) const {
76  const Notification* notification = NotificationUIManagerImpl::FindById(id);
77  if (notification)
78    return notification;
79  NotificationMap::const_iterator iter = profile_notifications_.find(id);
80  if (iter == profile_notifications_.end())
81    return NULL;
82  return &(iter->second->notification());
83}
84
85bool MessageCenterNotificationManager::CancelById(const std::string& id) {
86  // See if this ID hasn't been shown yet.
87  if (NotificationUIManagerImpl::CancelById(id))
88    return true;
89
90  // If it has been shown, remove it.
91  NotificationMap::iterator iter = profile_notifications_.find(id);
92  if (iter == profile_notifications_.end())
93    return false;
94
95  message_center_->RemoveNotification(id, /* by_user */ false);
96  return true;
97}
98
99std::set<std::string>
100MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
101    Profile* profile,
102    const GURL& source) {
103
104  std::set<std::string> notification_ids =
105      NotificationUIManagerImpl::GetAllIdsByProfileAndSourceOrigin(profile,
106                                                                   source);
107
108  for (NotificationMap::iterator iter = profile_notifications_.begin();
109       iter != profile_notifications_.end(); iter++) {
110    if ((*iter).second->notification().origin_url() == source &&
111        profile == (*iter).second->profile()) {
112      notification_ids.insert(iter->first);
113    }
114  }
115  return notification_ids;
116}
117
118bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
119    const GURL& source) {
120  // Same pattern as CancelById, but more complicated than the above
121  // because there may be multiple notifications from the same source.
122  bool removed = NotificationUIManagerImpl::CancelAllBySourceOrigin(source);
123
124  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
125       loopiter != profile_notifications_.end(); ) {
126    NotificationMap::iterator curiter = loopiter++;
127    if ((*curiter).second->notification().origin_url() == source) {
128      message_center_->RemoveNotification(curiter->first, /* by_user */ false);
129      removed = true;
130    }
131  }
132  return removed;
133}
134
135bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
136  // Same pattern as CancelAllBySourceOrigin.
137  bool removed = NotificationUIManagerImpl::CancelAllByProfile(profile);
138
139  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
140       loopiter != profile_notifications_.end(); ) {
141    NotificationMap::iterator curiter = loopiter++;
142    if (profile == (*curiter).second->profile()) {
143      message_center_->RemoveNotification(curiter->first, /* by_user */ false);
144      removed = true;
145    }
146  }
147  return removed;
148}
149
150void MessageCenterNotificationManager::CancelAll() {
151  NotificationUIManagerImpl::CancelAll();
152
153  message_center_->RemoveAllNotifications(/* by_user */ false);
154}
155
156////////////////////////////////////////////////////////////////////////////////
157// NotificationUIManagerImpl
158
159bool MessageCenterNotificationManager::ShowNotification(
160    const Notification& notification, Profile* profile) {
161  if (message_center_->IsMessageCenterVisible())
162    return false;
163
164  if (UpdateNotification(notification, profile))
165    return true;
166
167  AddProfileNotification(
168      new ProfileNotification(profile, notification, message_center_));
169  return true;
170}
171
172bool MessageCenterNotificationManager::UpdateNotification(
173    const Notification& notification, Profile* profile) {
174  // Only progress notification update can be reflected immediately in the
175  // message center.
176  bool update_progress_notification =
177      notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS;
178  bool is_message_center_visible = message_center_->IsMessageCenterVisible();
179  if (!update_progress_notification && is_message_center_visible)
180    return false;
181
182  const string16& replace_id = notification.replace_id();
183  if (replace_id.empty())
184    return false;
185
186  const GURL origin_url = notification.origin_url();
187  DCHECK(origin_url.is_valid());
188
189  // Since replace_id is provided by arbitrary JS, we need to use origin_url
190  // (which is an app url in case of app/extension) to scope the replace ids
191  // in the given profile.
192  for (NotificationMap::iterator iter = profile_notifications_.begin();
193       iter != profile_notifications_.end(); ++iter) {
194    ProfileNotification* old_notification = (*iter).second;
195    if (old_notification->notification().replace_id() == replace_id &&
196        old_notification->notification().origin_url() == origin_url &&
197        old_notification->profile() == profile) {
198      // Changing the type from non-progress to progress does not count towards
199      // the immediate update allowed in the message center.
200      if (update_progress_notification && is_message_center_visible &&
201          old_notification->notification().type() !=
202              message_center::NOTIFICATION_TYPE_PROGRESS) {
203        return false;
204      }
205
206      std::string old_id =
207          old_notification->notification().notification_id();
208      DCHECK(message_center_->HasNotification(old_id));
209
210      // Add/remove notification in the local list but just update the same
211      // one in MessageCenter.
212      delete old_notification;
213      profile_notifications_.erase(old_id);
214      ProfileNotification* new_notification =
215          new ProfileNotification(profile, notification, message_center_);
216      profile_notifications_[notification.notification_id()] = new_notification;
217
218      // Now pass a copy to message center.
219      scoped_ptr<message_center::Notification> message_center_notification(
220          make_scoped_ptr(new message_center::Notification(notification)));
221      message_center_notification->set_extension_id(
222          new_notification->GetExtensionId());
223      message_center_->UpdateNotification(old_id,
224                                          message_center_notification.Pass());
225
226      new_notification->StartDownloads();
227      return true;
228    }
229  }
230  return false;
231}
232
233////////////////////////////////////////////////////////////////////////////////
234// MessageCenter::Delegate
235
236void MessageCenterNotificationManager::DisableExtension(
237    const std::string& notification_id) {
238  ProfileNotification* profile_notification =
239      FindProfileNotification(notification_id);
240  if (!profile_notification)
241    return;
242
243  std::string extension_id = profile_notification->GetExtensionId();
244  DCHECK(!extension_id.empty());  // or UI should not have enabled the command.
245  DesktopNotificationService* service =
246      DesktopNotificationServiceFactory::GetForProfile(
247          profile_notification->profile());
248  message_center::NotifierId notifier_id(
249      message_center::NotifierId::APPLICATION, extension_id);
250  service->SetNotifierEnabled(notifier_id, false);
251}
252
253void MessageCenterNotificationManager::DisableNotificationsFromSource(
254    const std::string& notification_id) {
255  ProfileNotification* profile_notification =
256      FindProfileNotification(notification_id);
257  if (!profile_notification)
258    return;
259
260  // UI should not have enabled the command if there is no valid source.
261  DCHECK(profile_notification->notification().origin_url().is_valid());
262  DesktopNotificationService* service =
263      DesktopNotificationServiceFactory::GetForProfile(
264          profile_notification->profile());
265  if (profile_notification->notification().origin_url().scheme() ==
266      chrome::kChromeUIScheme) {
267    const std::string name =
268        profile_notification->notification().origin_url().host();
269    message_center::NotifierId notifier_id(
270        message_center::ParseSystemComponentName(name));
271    service->SetNotifierEnabled(notifier_id, false);
272  } else {
273    service->DenyPermission(profile_notification->notification().origin_url());
274  }
275}
276
277void MessageCenterNotificationManager::ShowSettings(
278    const std::string& notification_id) {
279  // The per-message-center Settings button passes an empty string.
280  if (notification_id.empty()) {
281    NOTIMPLEMENTED();
282    return;
283  }
284
285  ProfileNotification* profile_notification =
286      FindProfileNotification(notification_id);
287  if (!profile_notification)
288    return;
289
290  Browser* browser =
291      chrome::FindOrCreateTabbedBrowser(profile_notification->profile(),
292                                        chrome::HOST_DESKTOP_TYPE_NATIVE);
293  if (profile_notification->GetExtensionId().empty())
294    chrome::ShowContentSettings(browser, CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
295  else
296    chrome::ShowExtensions(browser, std::string());
297}
298
299////////////////////////////////////////////////////////////////////////////////
300// MessageCenter::Observer
301void MessageCenterNotificationManager::OnNotificationRemoved(
302    const std::string& notification_id,
303    bool by_user) {
304  // Do not call FindProfileNotification(). Some tests create notifications
305  // directly to MessageCenter, but this method will be called for the removals
306  // of such notifications.
307  NotificationMap::const_iterator iter =
308      profile_notifications_.find(notification_id);
309  if (iter != profile_notifications_.end())
310    RemoveProfileNotification(iter->second, by_user);
311
312#if defined(OS_WIN)
313  CheckFirstRunTimer();
314#endif
315}
316
317void MessageCenterNotificationManager::OnNotificationCenterClosed() {
318  // When the center is open it halts all notifications, so we need to listen
319  // for events indicating it's been closed.
320  CheckAndShowNotifications();
321#if defined(OS_WIN)
322  CheckFirstRunTimer();
323#endif
324}
325
326void MessageCenterNotificationManager::OnNotificationUpdated(
327    const std::string& notification_id) {
328#if defined(OS_WIN)
329  CheckFirstRunTimer();
330#endif
331}
332
333void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
334    message_center::MessageCenterTrayDelegate* delegate) {
335  tray_.reset(delegate);
336}
337
338void MessageCenterNotificationManager::Observe(
339    int type,
340    const content::NotificationSource& source,
341    const content::NotificationDetails& details) {
342  if (type == chrome::NOTIFICATION_FULLSCREEN_CHANGED) {
343    const bool is_fullscreen = *content::Details<bool>(details).ptr();
344
345    if (is_fullscreen && tray_.get() && tray_->GetMessageCenterTray())
346      tray_->GetMessageCenterTray()->HidePopupBubble();
347  } else {
348    NotificationUIManagerImpl::Observe(type, source, details);
349  }
350}
351
352////////////////////////////////////////////////////////////////////////////////
353// ImageDownloads
354
355MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
356    message_center::MessageCenter* message_center,
357    ImageDownloadsObserver* observer)
358    : message_center_(message_center),
359      pending_downloads_(0),
360      observer_(observer) {
361}
362
363MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
364
365void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
366    const Notification& notification) {
367  // In case all downloads are synchronous, assume a pending download.
368  AddPendingDownload();
369
370  // Notification primary icon.
371  StartDownloadWithImage(
372      notification,
373      &notification.icon(),
374      notification.icon_url(),
375      message_center::kNotificationIconSize,
376      base::Bind(&message_center::MessageCenter::SetNotificationIcon,
377                 base::Unretained(message_center_),
378                 notification.notification_id()));
379
380  // Notification image.
381  StartDownloadWithImage(
382      notification,
383      NULL,
384      notification.image_url(),
385      message_center::kNotificationPreferredImageSize,
386      base::Bind(&message_center::MessageCenter::SetNotificationImage,
387                 base::Unretained(message_center_),
388                 notification.notification_id()));
389
390  // Notification button icons.
391  StartDownloadWithImage(
392      notification,
393      NULL,
394      notification.button_one_icon_url(),
395      message_center::kNotificationButtonIconSize,
396      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
397                 base::Unretained(message_center_),
398                 notification.notification_id(),
399                 0));
400  StartDownloadWithImage(
401      notification,
402      NULL,
403      notification.button_two_icon_url(),
404      message_center::kNotificationButtonIconSize,
405      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
406                 base::Unretained(message_center_),
407                 notification.notification_id(),
408                 1));
409
410  // This should tell the observer we're done if everything was synchronous.
411  PendingDownloadCompleted();
412}
413
414void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
415    const Notification& notification,
416    const gfx::Image* image,
417    const GURL& url,
418    int size,
419    const SetImageCallback& callback) {
420  // Set the image directly if we have it.
421  if (image && !image->IsEmpty()) {
422    callback.Run(*image);
423    return;
424  }
425
426  // Leave the image null if there's no URL.
427  if (url.is_empty())
428    return;
429
430  content::RenderViewHost* host = notification.GetRenderViewHost();
431  if (!host) {
432    LOG(WARNING) << "Notification needs an image but has no RenderViewHost";
433    return;
434  }
435
436  content::WebContents* contents =
437      content::WebContents::FromRenderViewHost(host);
438  if (!contents) {
439    LOG(WARNING) << "Notification needs an image but has no WebContents";
440    return;
441  }
442
443  AddPendingDownload();
444
445  contents->DownloadImage(
446      url,
447      false,  // Not a favicon
448      size,  // Preferred size
449      0,  // No maximum size
450      base::Bind(
451          &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
452          AsWeakPtr(),
453          callback));
454}
455
456void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
457    const SetImageCallback& callback,
458    int download_id,
459    int http_status_code,
460    const GURL& image_url,
461    int requested_size,
462    const std::vector<SkBitmap>& bitmaps) {
463  PendingDownloadCompleted();
464
465  if (bitmaps.empty())
466    return;
467  gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
468  callback.Run(image);
469}
470
471// Private methods.
472
473void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
474  ++pending_downloads_;
475}
476
477void
478MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
479  DCHECK(pending_downloads_ > 0);
480  if (--pending_downloads_ == 0 && observer_)
481    observer_->OnDownloadsCompleted();
482}
483
484////////////////////////////////////////////////////////////////////////////////
485// ProfileNotification
486
487MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
488    Profile* profile,
489    const Notification& notification,
490    message_center::MessageCenter* message_center)
491    : profile_(profile),
492      notification_(notification),
493      downloads_(new ImageDownloads(message_center, this)) {
494  DCHECK(profile);
495}
496
497MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
498}
499
500void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
501  downloads_->StartDownloads(notification_);
502}
503
504void
505MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
506  notification_.DoneRendering();
507}
508
509std::string
510    MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
511  ExtensionInfoMap* extension_info_map =
512      extensions::ExtensionSystem::Get(profile())->info_map();
513  ExtensionSet extensions;
514  extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
515      notification().origin_url(), notification().process_id(),
516      extensions::APIPermission::kNotification, &extensions);
517
518  DesktopNotificationService* desktop_service =
519      DesktopNotificationServiceFactory::GetForProfile(profile());
520  for (ExtensionSet::const_iterator iter = extensions.begin();
521       iter != extensions.end(); ++iter) {
522    if (desktop_service->IsNotifierEnabled(message_center::NotifierId(
523            message_center::NotifierId::APPLICATION, (*iter)->id()))) {
524      return (*iter)->id();
525    }
526  }
527  return std::string();
528}
529
530////////////////////////////////////////////////////////////////////////////////
531// private
532
533void MessageCenterNotificationManager::AddProfileNotification(
534    ProfileNotification* profile_notification) {
535  const Notification& notification = profile_notification->notification();
536  std::string id = notification.notification_id();
537  // Notification ids should be unique.
538  DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
539  profile_notifications_[id] = profile_notification;
540
541  // Create the copy for message center, and ensure the extension ID is correct.
542  scoped_ptr<message_center::Notification> message_center_notification(
543      new message_center::Notification(notification));
544  message_center_notification->set_extension_id(
545      profile_notification->GetExtensionId());
546  message_center_->AddNotification(message_center_notification.Pass());
547
548  profile_notification->StartDownloads();
549}
550
551void MessageCenterNotificationManager::RemoveProfileNotification(
552    ProfileNotification* profile_notification,
553    bool by_user) {
554  profile_notification->notification().Close(by_user);
555  std::string id = profile_notification->notification().notification_id();
556  profile_notifications_.erase(id);
557  delete profile_notification;
558}
559
560MessageCenterNotificationManager::ProfileNotification*
561    MessageCenterNotificationManager::FindProfileNotification(
562        const std::string& id) const {
563  NotificationMap::const_iterator iter = profile_notifications_.find(id);
564  if (iter == profile_notifications_.end())
565    return NULL;
566
567  return (*iter).second;
568}
569