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