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