message_center_notification_manager.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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
28MessageCenterNotificationManager::MessageCenterNotificationManager(
29    message_center::MessageCenter* message_center)
30  : message_center_(message_center),
31    settings_controller_(new MessageCenterSettingsController) {
32  message_center_->SetDelegate(this);
33  message_center_->AddObserver(this);
34
35#if defined(OS_WIN) || defined(OS_MACOSX)
36  // On Windows and Mac, the notification manager owns the tray icon and views.
37  // Other platforms have global ownership and Create will return NULL.
38  tray_.reset(message_center::CreateMessageCenterTray());
39#endif
40}
41
42MessageCenterNotificationManager::~MessageCenterNotificationManager() {
43  message_center_->RemoveObserver(this);
44}
45
46
47////////////////////////////////////////////////////////////////////////////////
48// NotificationUIManager
49
50bool MessageCenterNotificationManager::DoesIdExist(const std::string& id) {
51  if (NotificationUIManagerImpl::DoesIdExist(id))
52    return true;
53  NotificationMap::iterator iter = profile_notifications_.find(id);
54  if (iter == profile_notifications_.end())
55    return false;
56  return true;
57}
58
59bool MessageCenterNotificationManager::CancelById(const std::string& id) {
60  // See if this ID hasn't been shown yet.
61  if (NotificationUIManagerImpl::CancelById(id))
62    return true;
63
64  // If it has been shown, remove it.
65  NotificationMap::iterator iter = profile_notifications_.find(id);
66  if (iter == profile_notifications_.end())
67    return false;
68
69  message_center_->RemoveNotification(id, /* by_user */ false);
70  return true;
71}
72
73bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
74    const GURL& source) {
75  // Same pattern as CancelById, but more complicated than the above
76  // because there may be multiple notifications from the same source.
77  bool removed = NotificationUIManagerImpl::CancelAllBySourceOrigin(source);
78
79  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
80       loopiter != profile_notifications_.end(); ) {
81    NotificationMap::iterator curiter = loopiter++;
82    if ((*curiter).second->notification().origin_url() == source) {
83      message_center_->RemoveNotification(curiter->first, /* by_user */ false);
84      removed = true;
85    }
86  }
87  return removed;
88}
89
90bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
91  // Same pattern as CancelAllBySourceOrigin.
92  bool removed = NotificationUIManagerImpl::CancelAllByProfile(profile);
93
94  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
95       loopiter != profile_notifications_.end(); ) {
96    NotificationMap::iterator curiter = loopiter++;
97    if ((*curiter).second->profile() == profile) {
98      message_center_->RemoveNotification(curiter->first, /* by_user */ false);
99      removed = true;
100    }
101  }
102  return removed;
103}
104
105void MessageCenterNotificationManager::CancelAll() {
106  NotificationUIManagerImpl::CancelAll();
107
108  message_center_->RemoveAllNotifications(/* by_user */ false);
109}
110
111////////////////////////////////////////////////////////////////////////////////
112// NotificationUIManagerImpl
113
114bool MessageCenterNotificationManager::ShowNotification(
115    const Notification& notification, Profile* profile) {
116  // There is always space in MessageCenter, it never rejects Notifications.
117  AddProfileNotification(
118      new ProfileNotification(profile, notification, message_center_));
119  return true;
120}
121
122bool MessageCenterNotificationManager::UpdateNotification(
123    const Notification& notification, Profile* profile) {
124  const string16& replace_id = notification.replace_id();
125  DCHECK(!replace_id.empty());
126  const GURL origin_url = notification.origin_url();
127  DCHECK(origin_url.is_valid());
128
129  // Since replace_id is provided by arbitrary JS, we need to use origin_url
130  // (which is an app url in case of app/extension) to scope the replace ids
131  // in the given profile.
132  for (NotificationMap::iterator iter = profile_notifications_.begin();
133       iter != profile_notifications_.end(); ++iter) {
134    ProfileNotification* old_notification = (*iter).second;
135    if (old_notification->notification().replace_id() == replace_id &&
136        old_notification->notification().origin_url() == origin_url &&
137        old_notification->profile()->IsSameProfile(profile)) {
138      std::string old_id =
139          old_notification->notification().notification_id();
140      DCHECK(message_center_->HasNotification(old_id));
141
142      // Add/remove notification in the local list but just update the same
143      // one in MessageCenter.
144      old_notification->notification().Close(false); // Not by user.
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      message_center_->UpdateNotification(old_id,
151                                          notification.notification_id(),
152                                          notification.title(),
153                                          notification.body(),
154                                          notification.optional_fields(),
155                                          notification.delegate());
156      new_notification->StartDownloads();
157      return true;
158    }
159  }
160  return false;
161}
162
163////////////////////////////////////////////////////////////////////////////////
164// MessageCenter::Delegate
165
166void MessageCenterNotificationManager::DisableExtension(
167    const std::string& notification_id) {
168  ProfileNotification* profile_notification =
169      FindProfileNotification(notification_id);
170  if (!profile_notification)
171    return;
172
173  std::string extension_id = profile_notification->GetExtensionId();
174  DCHECK(!extension_id.empty());  // or UI should not have enabled the command.
175  DesktopNotificationService* service =
176      DesktopNotificationServiceFactory::GetForProfile(
177          profile_notification->profile());
178  service->SetExtensionEnabled(extension_id, false);
179}
180
181void MessageCenterNotificationManager::DisableNotificationsFromSource(
182    const std::string& notification_id) {
183  ProfileNotification* profile_notification =
184      FindProfileNotification(notification_id);
185  if (!profile_notification)
186    return;
187
188  // UI should not have enabled the command if there is no valid source.
189  DCHECK(profile_notification->notification().origin_url().is_valid());
190  DesktopNotificationService* service =
191      DesktopNotificationServiceFactory::GetForProfile(
192          profile_notification->profile());
193  if (profile_notification->notification().origin_url().scheme() ==
194      chrome::kChromeUIScheme) {
195    const std::string name =
196        profile_notification->notification().origin_url().host();
197    const message_center::Notifier::SystemComponentNotifierType type =
198        message_center::ParseSystemComponentName(name);
199    service->SetSystemComponentEnabled(type, false);
200  } else {
201    service->DenyPermission(profile_notification->notification().origin_url());
202  }
203}
204
205void MessageCenterNotificationManager::ShowSettings(
206    const std::string& notification_id) {
207  // The per-message-center Settings button passes an empty string.
208  if (notification_id.empty()) {
209    NOTIMPLEMENTED();
210    return;
211  }
212
213  ProfileNotification* profile_notification =
214      FindProfileNotification(notification_id);
215  if (!profile_notification)
216    return;
217
218  Browser* browser =
219      chrome::FindOrCreateTabbedBrowser(profile_notification->profile(),
220                                        chrome::HOST_DESKTOP_TYPE_NATIVE);
221  if (profile_notification->GetExtensionId().empty())
222    chrome::ShowContentSettings(browser, CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
223  else
224    chrome::ShowExtensions(browser, std::string());
225}
226
227void MessageCenterNotificationManager::ShowSettingsDialog(
228    gfx::NativeView context) {
229  settings_controller_->ShowSettingsDialog(context);
230}
231
232////////////////////////////////////////////////////////////////////////////////
233// MessageCenter::Observer
234void MessageCenterNotificationManager::OnNotificationRemoved(
235    const std::string& notification_id,
236    bool by_user) {
237  // Do not call FindProfileNotification(). Some tests create notifications
238  // directly to MessageCenter, but this method will be called for the removals
239  // of such notifications.
240  NotificationMap::const_iterator iter =
241      profile_notifications_.find(notification_id);
242  if (iter != profile_notifications_.end())
243    RemoveProfileNotification(iter->second, by_user);
244}
245
246////////////////////////////////////////////////////////////////////////////////
247// ImageDownloads
248
249MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
250    message_center::MessageCenter* message_center,
251    ImageDownloadsObserver* observer)
252    : message_center_(message_center),
253      pending_downloads_(0),
254      observer_(observer) {
255}
256
257MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
258
259void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
260    const Notification& notification) {
261  // In case all downloads are synchronous, assume a pending download.
262  AddPendingDownload();
263
264  // Notification primary icon.
265  StartDownloadWithImage(
266      notification,
267      &notification.icon(),
268      notification.icon_url(),
269      message_center::kNotificationIconSize,
270      base::Bind(&message_center::MessageCenter::SetNotificationIcon,
271                 base::Unretained(message_center_),
272                 notification.notification_id()));
273
274  // Notification image.
275  StartDownloadByKey(
276      notification,
277      message_center::kImageUrlKey,
278      message_center::kNotificationPreferredImageSize,
279      base::Bind(&message_center::MessageCenter::SetNotificationImage,
280                 base::Unretained(message_center_),
281                 notification.notification_id()));
282
283  // Notification button icons.
284  StartDownloadByKey(
285      notification,
286      message_center::kButtonOneIconUrlKey,
287      message_center::kNotificationButtonIconSize,
288      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
289                 base::Unretained(message_center_),
290                 notification.notification_id(), 0));
291  StartDownloadByKey(
292      notification, message_center::kButtonTwoIconUrlKey,
293      message_center::kNotificationButtonIconSize,
294      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
295                 base::Unretained(message_center_),
296                 notification.notification_id(), 1));
297
298  // This should tell the observer we're done if everything was synchronous.
299  PendingDownloadCompleted();
300}
301
302void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
303    const Notification& notification,
304    const gfx::Image* image,
305    const GURL& url,
306    int size,
307    const SetImageCallback& callback) {
308  // Set the image directly if we have it.
309  if (image && !image->IsEmpty()) {
310    callback.Run(*image);
311    return;
312  }
313
314  // Leave the image null if there's no URL.
315  if (url.is_empty())
316    return;
317
318  content::RenderViewHost* host = notification.GetRenderViewHost();
319  if (!host) {
320    LOG(WARNING) << "Notification needs an image but has no RenderViewHost";
321    return;
322  }
323
324  content::WebContents* contents =
325      content::WebContents::FromRenderViewHost(host);
326  if (!contents) {
327    LOG(WARNING) << "Notification needs an image but has no WebContents";
328    return;
329  }
330
331  AddPendingDownload();
332
333  contents->DownloadImage(
334      url,
335      false,
336      size,
337      base::Bind(
338          &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
339          AsWeakPtr(),
340          callback));
341}
342
343void MessageCenterNotificationManager::ImageDownloads::StartDownloadByKey(
344    const Notification& notification,
345    const char* key,
346    int size,
347    const SetImageCallback& callback) {
348  const base::DictionaryValue* optional_fields = notification.optional_fields();
349  if (optional_fields && optional_fields->HasKey(key)) {
350    string16 url;
351    optional_fields->GetString(key, &url);
352    StartDownloadWithImage(notification, NULL, GURL(url), size, callback);
353  }
354}
355
356void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
357    const SetImageCallback& callback,
358    int download_id,
359    int http_status_code,
360    const GURL& image_url,
361    int requested_size,
362    const std::vector<SkBitmap>& bitmaps) {
363  PendingDownloadCompleted();
364
365  if (bitmaps.empty())
366    return;
367  gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
368  callback.Run(image);
369}
370
371// Private methods.
372
373void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
374  ++pending_downloads_;
375}
376
377void
378MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
379  DCHECK(pending_downloads_ > 0);
380  if (--pending_downloads_ == 0 && observer_)
381    observer_->OnDownloadsCompleted();
382}
383
384////////////////////////////////////////////////////////////////////////////////
385// ProfileNotification
386
387MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
388    Profile* profile,
389    const Notification& notification,
390    message_center::MessageCenter* message_center)
391    : profile_(profile),
392      notification_(notification),
393      downloads_(new ImageDownloads(message_center, this)) {
394  DCHECK(profile);
395}
396
397MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
398}
399
400void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
401  downloads_->StartDownloads(notification_);
402}
403
404void
405MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
406  notification_.DoneRendering();
407}
408
409std::string
410    MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
411  ExtensionInfoMap* extension_info_map =
412      extensions::ExtensionSystem::Get(profile())->info_map();
413  ExtensionSet extensions;
414  extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
415      notification().origin_url(), notification().process_id(),
416      extensions::APIPermission::kNotification, &extensions);
417
418  DesktopNotificationService* desktop_service =
419      DesktopNotificationServiceFactory::GetForProfile(profile());
420  for (ExtensionSet::const_iterator iter = extensions.begin();
421       iter != extensions.end(); ++iter) {
422    if (desktop_service->IsExtensionEnabled((*iter)->id()))
423      return (*iter)->id();
424  }
425  return std::string();
426}
427
428////////////////////////////////////////////////////////////////////////////////
429// private
430
431void MessageCenterNotificationManager::AddProfileNotification(
432    ProfileNotification* profile_notification) {
433  const Notification& notification = profile_notification->notification();
434  std::string id = notification.notification_id();
435  // Notification ids should be unique.
436  DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
437  profile_notifications_[id] = profile_notification;
438
439  message_center_->AddNotification(notification.type(),
440                                   notification.notification_id(),
441                                   notification.title(),
442                                   notification.body(),
443                                   notification.display_source(),
444                                   profile_notification->GetExtensionId(),
445                                   notification.optional_fields(),
446                                   notification.delegate());
447  profile_notification->StartDownloads();
448}
449
450void MessageCenterNotificationManager::RemoveProfileNotification(
451    ProfileNotification* profile_notification,
452    bool by_user) {
453  profile_notification->notification().Close(by_user);
454  std::string id = profile_notification->notification().notification_id();
455  profile_notifications_.erase(id);
456  delete profile_notification;
457}
458
459MessageCenterNotificationManager::ProfileNotification*
460    MessageCenterNotificationManager::FindProfileNotification(
461        const std::string& id) const {
462  NotificationMap::const_iterator iter = profile_notifications_.find(id);
463  if (iter == profile_notifications_.end())
464    return NULL;
465
466  return (*iter).second;
467}
468