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