message_center_notification_manager.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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.
48const int kFirstRunIdleDelaySeconds = 1;
49#endif
50
51MessageCenterNotificationManager::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
91MessageCenterNotificationManager::~MessageCenterNotificationManager() {
92  message_center_->SetNotifierSettingsProvider(NULL);
93  message_center_->RemoveObserver(this);
94}
95
96void 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
107void 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
119bool 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().notification_id();
141      DCHECK(message_center_->HasNotification(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.notification_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
164const 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
172bool 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
184std::set<std::string>
185MessageCenterNotificationManager::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
200bool 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
219bool 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
236void MessageCenterNotificationManager::CancelAll() {
237  message_center_->RemoveAllNotifications(/* by_user */ false);
238}
239
240////////////////////////////////////////////////////////////////////////////////
241// MessageCenter::Observer
242void 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
255void 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
263void MessageCenterNotificationManager::OnNotificationUpdated(
264    const std::string& notification_id) {
265#if defined(OS_WIN)
266  CheckFirstRunTimer();
267#endif
268}
269
270void 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
284void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
285    message_center::MessageCenterTrayDelegate* delegate) {
286  tray_.reset(delegate);
287}
288
289void 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
304MessageCenterNotificationManager::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
312MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
313
314void 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      &notification.icon(),
323      notification.icon_url(),
324      base::Bind(&message_center::MessageCenter::SetNotificationIcon,
325                 base::Unretained(message_center_),
326                 notification.notification_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.notification_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.notification_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.notification_id(),
353                 1));
354
355  // This should tell the observer we're done if everything was synchronous.
356  PendingDownloadCompleted();
357}
358
359void 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
392void 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
409void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
410  ++pending_downloads_;
411}
412
413void
414MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
415  DCHECK(pending_downloads_ > 0);
416  if (--pending_downloads_ == 0 && observer_)
417    observer_->OnDownloadsCompleted();
418}
419
420////////////////////////////////////////////////////////////////////////////////
421// ProfileNotification
422
423MessageCenterNotificationManager::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
436MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
437}
438
439void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
440  downloads_->StartDownloads(notification_);
441}
442
443void
444MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
445  notification_.DoneRendering();
446}
447
448std::string
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
472void MessageCenterNotificationManager::AddProfileNotification(
473    ProfileNotification* profile_notification) {
474  const Notification& notification = profile_notification->notification();
475  std::string id = notification.notification_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
488void MessageCenterNotificationManager::RemoveProfileNotification(
489    ProfileNotification* profile_notification) {
490  std::string id = profile_notification->notification().notification_id();
491  profile_notifications_.erase(id);
492  delete profile_notification;
493}
494
495MessageCenterNotificationManager::ProfileNotification*
496    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