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