message_center_notification_manager.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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_constants.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      new_notification->StartDownloads();
156      return true;
157    }
158  }
159  return false;
160}
161
162////////////////////////////////////////////////////////////////////////////////
163// MessageCenter::Delegate
164
165void MessageCenterNotificationManager::DisableExtension(
166    const std::string& notification_id) {
167  ProfileNotification* profile_notification =
168      FindProfileNotification(notification_id);
169  if (!profile_notification)
170    return;
171
172  std::string extension_id = profile_notification->GetExtensionId();
173  DCHECK(!extension_id.empty());  // or UI should not have enabled the command.
174  DesktopNotificationService* service =
175      DesktopNotificationServiceFactory::GetForProfile(
176          profile_notification->profile());
177  service->SetExtensionEnabled(extension_id, false);
178}
179
180void MessageCenterNotificationManager::DisableNotificationsFromSource(
181    const std::string& notification_id) {
182  ProfileNotification* profile_notification =
183      FindProfileNotification(notification_id);
184  if (!profile_notification)
185    return;
186
187  // UI should not have enabled the command if there is no valid source.
188  DCHECK(profile_notification->notification().origin_url().is_valid());
189  DesktopNotificationService* service =
190      DesktopNotificationServiceFactory::GetForProfile(
191          profile_notification->profile());
192  if (profile_notification->notification().origin_url().scheme() ==
193      chrome::kChromeUIScheme) {
194    const std::string name =
195        profile_notification->notification().origin_url().host();
196    const message_center::Notifier::SystemComponentNotifierType type =
197        message_center::ParseSystemComponentName(name);
198    service->SetSystemComponentEnabled(type, false);
199  } else {
200    service->DenyPermission(profile_notification->notification().origin_url());
201  }
202}
203
204void MessageCenterNotificationManager::ShowSettings(
205    const std::string& notification_id) {
206  // The per-message-center Settings button passes an empty string.
207  if (notification_id.empty()) {
208    NOTIMPLEMENTED();
209    return;
210  }
211
212  ProfileNotification* profile_notification =
213      FindProfileNotification(notification_id);
214  if (!profile_notification)
215    return;
216
217  Browser* browser =
218      chrome::FindOrCreateTabbedBrowser(profile_notification->profile(),
219                                        chrome::HOST_DESKTOP_TYPE_NATIVE);
220  if (profile_notification->GetExtensionId().empty())
221    chrome::ShowContentSettings(browser, CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
222  else
223    chrome::ShowExtensions(browser, std::string());
224}
225
226void MessageCenterNotificationManager::ShowSettingsDialog(
227    gfx::NativeView context) {
228  settings_controller_->ShowSettingsDialog(context);
229}
230
231////////////////////////////////////////////////////////////////////////////////
232// MessageCenter::Observer
233void MessageCenterNotificationManager::OnNotificationRemoved(
234    const std::string& notification_id,
235    bool by_user) {
236  // Do not call FindProfileNotification(). Some tests create notifications
237  // directly to MessageCenter, but this method will be called for the removals
238  // of such notifications.
239  NotificationMap::const_iterator iter =
240      profile_notifications_.find(notification_id);
241  if (iter != profile_notifications_.end())
242    RemoveProfileNotification(iter->second, by_user);
243}
244
245void MessageCenterNotificationManager::OnNotificationClicked(
246    const std::string& notification_id) {
247  ProfileNotification* profile_notification =
248      FindProfileNotification(notification_id);
249  if (!profile_notification)
250    return;
251  profile_notification->notification().Click();
252}
253
254void MessageCenterNotificationManager::OnNotificationButtonClicked(
255    const std::string& notification_id,
256    int button_index) {
257  ProfileNotification* profile_notification =
258      FindProfileNotification(notification_id);
259  if (!profile_notification)
260    return;
261  profile_notification->notification().ButtonClick(button_index);
262}
263
264void MessageCenterNotificationManager::OnNotificationDisplayed(
265    const std::string& notification_id) {
266  FindProfileNotification(notification_id)->notification().Display();
267}
268
269////////////////////////////////////////////////////////////////////////////////
270// ImageDownloads
271
272MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
273    message_center::MessageCenter* message_center,
274    ImageDownloadsObserver* observer)
275    : message_center_(message_center),
276      pending_downloads_(0),
277      observer_(observer) {
278}
279
280MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
281
282void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
283    const Notification& notification) {
284  // In case all downloads are synchronous, assume a pending download.
285  AddPendingDownload();
286
287  // Notification primary icon.
288  StartDownloadWithImage(
289      notification,
290      &notification.icon(),
291      notification.icon_url(),
292      message_center::kNotificationIconSize,
293      base::Bind(&message_center::MessageCenter::SetNotificationIcon,
294                 base::Unretained(message_center_),
295                 notification.notification_id()));
296
297  // Notification image.
298  StartDownloadByKey(
299      notification,
300      message_center::kImageUrlKey,
301      message_center::kNotificationPreferredImageSize,
302      base::Bind(&message_center::MessageCenter::SetNotificationImage,
303                 base::Unretained(message_center_),
304                 notification.notification_id()));
305
306  // Notification button icons.
307  StartDownloadByKey(
308      notification,
309      message_center::kButtonOneIconUrlKey,
310      message_center::kNotificationButtonIconSize,
311      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
312                 base::Unretained(message_center_),
313                 notification.notification_id(), 0));
314  StartDownloadByKey(
315      notification, message_center::kButtonTwoIconUrlKey,
316      message_center::kNotificationButtonIconSize,
317      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
318                 base::Unretained(message_center_),
319                 notification.notification_id(), 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    int size,
330    const SetImageCallback& callback) {
331  // Set the image directly if we have it.
332  if (image && !image->IsEmpty()) {
333    callback.Run(*image);
334    return;
335  }
336
337  // Leave the image null if there's no URL.
338  if (url.is_empty())
339    return;
340
341  content::RenderViewHost* host = notification.GetRenderViewHost();
342  if (!host) {
343    LOG(WARNING) << "Notification needs an image but has no RenderViewHost";
344    return;
345  }
346
347  content::WebContents* contents =
348      content::WebContents::FromRenderViewHost(host);
349  if (!contents) {
350    LOG(WARNING) << "Notification needs an image but has no WebContents";
351    return;
352  }
353
354  AddPendingDownload();
355
356  contents->DownloadImage(
357      url,
358      false,
359      size,
360      base::Bind(
361          &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
362          AsWeakPtr(),
363          callback));
364}
365
366void MessageCenterNotificationManager::ImageDownloads::StartDownloadByKey(
367    const Notification& notification,
368    const char* key,
369    int size,
370    const SetImageCallback& callback) {
371  const base::DictionaryValue* optional_fields = notification.optional_fields();
372  if (optional_fields && optional_fields->HasKey(key)) {
373    string16 url;
374    optional_fields->GetString(key, &url);
375    StartDownloadWithImage(notification, NULL, GURL(url), size, callback);
376  }
377}
378
379void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
380    const SetImageCallback& callback,
381    int download_id,
382    const GURL& image_url,
383    int requested_size,
384    const std::vector<SkBitmap>& bitmaps) {
385  PendingDownloadCompleted();
386
387  if (bitmaps.empty())
388    return;
389  gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
390  callback.Run(image);
391}
392
393// Private methods.
394
395void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
396  ++pending_downloads_;
397}
398
399void
400MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
401  DCHECK(pending_downloads_ > 0);
402  if (--pending_downloads_ == 0 && observer_)
403    observer_->OnDownloadsCompleted();
404}
405
406////////////////////////////////////////////////////////////////////////////////
407// ProfileNotification
408
409MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
410    Profile* profile,
411    const Notification& notification,
412    message_center::MessageCenter* message_center)
413    : profile_(profile),
414      notification_(notification),
415      downloads_(new ImageDownloads(message_center, this)) {
416  DCHECK(profile);
417}
418
419MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
420}
421
422void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
423  downloads_->StartDownloads(notification_);
424}
425
426void
427MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
428  notification_.DoneRendering();
429}
430
431std::string
432    MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
433  ExtensionInfoMap* extension_info_map =
434      extensions::ExtensionSystem::Get(profile())->info_map();
435  ExtensionSet extensions;
436  extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
437      notification().origin_url(), notification().process_id(),
438      extensions::APIPermission::kNotification, &extensions);
439
440  DesktopNotificationService* desktop_service =
441      DesktopNotificationServiceFactory::GetForProfile(profile());
442  for (ExtensionSet::const_iterator iter = extensions.begin();
443       iter != extensions.end(); ++iter) {
444    if (desktop_service->IsExtensionEnabled((*iter)->id()))
445      return (*iter)->id();
446  }
447  return std::string();
448}
449
450////////////////////////////////////////////////////////////////////////////////
451// private
452
453void MessageCenterNotificationManager::AddProfileNotification(
454    ProfileNotification* profile_notification) {
455  const Notification& notification = profile_notification->notification();
456  std::string id = notification.notification_id();
457  // Notification ids should be unique.
458  DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
459  profile_notifications_[id] = profile_notification;
460
461  message_center_->AddNotification(notification.type(),
462                                   notification.notification_id(),
463                                   notification.title(),
464                                   notification.body(),
465                                   notification.display_source(),
466                                   profile_notification->GetExtensionId(),
467                                   notification.optional_fields());
468  profile_notification->StartDownloads();
469}
470
471void MessageCenterNotificationManager::RemoveProfileNotification(
472    ProfileNotification* profile_notification,
473    bool by_user) {
474  profile_notification->notification().Close(by_user);
475  std::string id = profile_notification->notification().notification_id();
476  profile_notifications_.erase(id);
477  delete profile_notification;
478}
479
480MessageCenterNotificationManager::ProfileNotification*
481    MessageCenterNotificationManager::FindProfileNotification(
482        const std::string& id) const {
483  NotificationMap::const_iterator iter = profile_notifications_.find(id);
484  if (iter == profile_notifications_.end())
485    return NULL;
486
487  return (*iter).second;
488}
489