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