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