message_center_notification_manager.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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_service.h"
11#include "chrome/browser/notifications/desktop_notification_service.h"
12#include "chrome/browser/notifications/desktop_notification_service_factory.h"
13#include "chrome/browser/notifications/message_center_settings_controller.h"
14#include "chrome/browser/notifications/notification.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/ui/browser_finder.h"
17#include "chrome/browser/ui/chrome_pages.h"
18#include "chrome/browser/ui/host_desktop.h"
19#include "chrome/common/extensions/extension_set.h"
20#include "chrome/common/pref_names.h"
21#include "content/public/browser/web_contents.h"
22#include "ui/message_center/message_center_constants.h"
23#include "ui/message_center/message_center_tray.h"
24
25MessageCenterNotificationManager::MessageCenterNotificationManager(
26    message_center::MessageCenter* message_center)
27  : message_center_(message_center),
28    settings_controller_(new MessageCenterSettingsController) {
29  message_center_->SetDelegate(this);
30
31#if !defined(OS_CHROMEOS)
32  // On Windows, the notification manager owns the tray icon and views.  Other
33  // platforms have global ownership and Create will return NULL.
34  tray_.reset(message_center::CreateMessageCenterTray());
35#endif
36}
37
38MessageCenterNotificationManager::~MessageCenterNotificationManager() {
39}
40
41
42////////////////////////////////////////////////////////////////////////////////
43// NotificationUIManager
44
45bool MessageCenterNotificationManager::DoesIdExist(const std::string& id) {
46  if (NotificationUIManagerImpl::DoesIdExist(id))
47    return true;
48  NotificationMap::iterator iter = profile_notifications_.find(id);
49  if (iter == profile_notifications_.end())
50    return false;
51  return true;
52}
53
54bool MessageCenterNotificationManager::CancelById(const std::string& id) {
55  // See if this ID hasn't been shown yet.
56  if (NotificationUIManagerImpl::CancelById(id))
57    return true;
58
59  // If it has been shown, remove it.
60  NotificationMap::iterator iter = profile_notifications_.find(id);
61  if (iter == profile_notifications_.end())
62    return false;
63
64  RemoveProfileNotification((*iter).second, false);
65  return true;
66}
67
68bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
69    const GURL& source) {
70  // Same pattern as CancelById, but more complicated than the above
71  // because there may be multiple notifications from the same source.
72  bool removed = NotificationUIManagerImpl::CancelAllBySourceOrigin(source);
73
74  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
75       loopiter != profile_notifications_.end(); ) {
76    NotificationMap::iterator curiter = loopiter++;
77    if ((*curiter).second->notification().origin_url() == source) {
78      // This action occurs when extension is unloaded. Closing notifications
79      // is not by user, so |false|.
80      RemoveProfileNotification((*curiter).second, false);
81      removed = true;
82    }
83  }
84  return removed;
85}
86
87bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
88  // Same pattern as CancelAllBySourceOrigin.
89  bool removed = NotificationUIManagerImpl::CancelAllByProfile(profile);
90
91  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
92       loopiter != profile_notifications_.end(); ) {
93    NotificationMap::iterator curiter = loopiter++;
94    if ((*curiter).second->profile() == profile) {
95      // This action occurs when profile is unloaded. Closing notifications is
96      // not by user, so |false|.
97      RemoveProfileNotification((*curiter).second, false);
98      removed = true;
99    }
100  }
101  return removed;
102}
103
104void MessageCenterNotificationManager::CancelAll() {
105  NotificationUIManagerImpl::CancelAll();
106
107  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
108       loopiter != profile_notifications_.end(); ) {
109    // This action occurs when Chrome is terminating. Closing notifications is
110    // not by user, so |false|.
111    RemoveProfileNotification((*loopiter++).second, false);
112  }
113}
114
115////////////////////////////////////////////////////////////////////////////////
116// NotificationUIManagerImpl
117
118bool MessageCenterNotificationManager::ShowNotification(
119    const Notification& notification, Profile* profile) {
120  // There is always space in MessageCenter, it never rejects Notifications.
121  AddProfileNotification(
122      new ProfileNotification(profile, notification, message_center_));
123  return true;
124}
125
126bool MessageCenterNotificationManager::UpdateNotification(
127    const Notification& notification, Profile* profile) {
128  const string16& replace_id = notification.replace_id();
129  DCHECK(!replace_id.empty());
130  const GURL origin_url = notification.origin_url();
131  DCHECK(origin_url.is_valid());
132
133  // Since replace_id is provided by arbitrary JS, we need to use origin_url
134  // (which is an app url in case of app/extension) to scope the replace ids
135  // in the given profile.
136  for (NotificationMap::iterator iter = profile_notifications_.begin();
137       iter != profile_notifications_.end(); ++iter) {
138    ProfileNotification* old_notification = (*iter).second;
139    if (old_notification->notification().replace_id() == replace_id &&
140        old_notification->notification().origin_url() == origin_url &&
141        old_notification->profile()->IsSameProfile(profile)) {
142      std::string old_id =
143          old_notification->notification().notification_id();
144      DCHECK(message_center_->notification_list()->HasNotification(old_id));
145
146      // Add/remove notification in the local list but just update the same
147      // one in MessageCenter.
148      old_notification->notification().Close(false); // Not by user.
149      delete old_notification;
150      profile_notifications_.erase(old_id);
151      ProfileNotification* new_notification =
152          new ProfileNotification(profile, notification, message_center_);
153      profile_notifications_[notification.notification_id()] = new_notification;
154      message_center_->UpdateNotification(old_id,
155                                          notification.notification_id(),
156                                          notification.title(),
157                                          notification.body(),
158                                          notification.optional_fields());
159      new_notification->StartDownloads();
160      notification.Display();
161      return true;
162    }
163  }
164  return false;
165}
166
167////////////////////////////////////////////////////////////////////////////////
168// MessageCenter::Delegate
169
170void MessageCenterNotificationManager::DisableExtension(
171    const std::string& notification_id) {
172  ProfileNotification* profile_notification =
173      FindProfileNotification(notification_id);
174  std::string extension_id = profile_notification->GetExtensionId();
175  DCHECK(!extension_id.empty());  // or UI should not have enabled the command.
176  profile_notification->profile()->GetExtensionService()->DisableExtension(
177      extension_id, extensions::Extension::DISABLE_USER_ACTION);
178}
179
180void MessageCenterNotificationManager::DisableNotificationsFromSource(
181    const std::string& notification_id) {
182  ProfileNotification* profile_notification =
183      FindProfileNotification(notification_id);
184  // UI should not have enabled the command if there is no valid source.
185  DCHECK(profile_notification->notification().origin_url().is_valid());
186  DesktopNotificationService* service =
187      DesktopNotificationServiceFactory::GetForProfile(
188          profile_notification->profile());
189  service->DenyPermission(profile_notification->notification().origin_url());
190}
191
192void MessageCenterNotificationManager::NotificationRemoved(
193    const std::string& notification_id,
194    bool by_user) {
195  RemoveProfileNotification(FindProfileNotification(notification_id), by_user);
196}
197
198void MessageCenterNotificationManager::ShowSettings(
199    const std::string& notification_id) {
200  // The per-message-center Settings button passes an empty string.
201  if (notification_id.empty()) {
202    NOTIMPLEMENTED();
203    return;
204  }
205
206  ProfileNotification* profile_notification =
207      FindProfileNotification(notification_id);
208  Browser* browser =
209      chrome::FindOrCreateTabbedBrowser(profile_notification->profile(),
210                                        chrome::HOST_DESKTOP_TYPE_NATIVE);
211  if (profile_notification->GetExtensionId().empty())
212    chrome::ShowContentSettings(browser, CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
213  else
214    chrome::ShowExtensions(browser, std::string());
215}
216
217void MessageCenterNotificationManager::ShowSettingsDialog(
218    gfx::NativeView context) {
219  settings_controller_->ShowSettingsDialog(context);
220}
221
222void MessageCenterNotificationManager::OnClicked(
223    const std::string& notification_id) {
224  FindProfileNotification(notification_id)->notification().Click();
225}
226
227void MessageCenterNotificationManager::OnButtonClicked(
228    const std::string& notification_id, int button_index) {
229  FindProfileNotification(notification_id)->notification().ButtonClick(
230      button_index);
231}
232
233////////////////////////////////////////////////////////////////////////////////
234// ImageDownloads
235
236MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
237    message_center::MessageCenter* message_center)
238    : message_center_(message_center) {
239}
240
241MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
242
243void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
244    const Notification& notification) {
245  // Notification primary icon.
246  StartDownloadWithImage(
247      notification,
248      &notification.icon(),
249      notification.icon_url(),
250      message_center::kNotificationIconSize,
251      base::Bind(&message_center::MessageCenter::SetNotificationIcon,
252                 base::Unretained(message_center_),
253                 notification.notification_id()));
254
255  // Notification image.
256  StartDownloadByKey(
257      notification,
258      message_center::kImageUrlKey,
259      message_center::kNotificationPreferredImageSize,
260      base::Bind(&message_center::MessageCenter::SetNotificationImage,
261                 base::Unretained(message_center_),
262                 notification.notification_id()));
263
264  // Notification button icons.
265  StartDownloadByKey(
266      notification,
267      message_center::kButtonOneIconUrlKey,
268      message_center::kNotificationButtonIconSize,
269      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
270                 base::Unretained(message_center_),
271                 notification.notification_id(), 0));
272  StartDownloadByKey(
273      notification, message_center::kButtonTwoIconUrlKey,
274      message_center::kNotificationButtonIconSize,
275      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
276                 base::Unretained(message_center_),
277                 notification.notification_id(), 1));
278}
279
280void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
281    const Notification& notification,
282    const gfx::Image* image,
283    const GURL& url,
284    int size,
285    const SetImageCallback& callback) {
286  // Set the image directly if we have it.
287  if (image && !image->IsEmpty()) {
288    callback.Run(*image);
289    return;
290  }
291
292  // Leave the image null if there's no URL.
293  if (url.is_empty())
294    return;
295
296  content::RenderViewHost* host = notification.GetRenderViewHost();
297  if (!host) {
298    LOG(WARNING) << "Notification needs an image but has no RenderViewHost";
299    return;
300  }
301
302  content::WebContents* contents =
303      content::WebContents::FromRenderViewHost(host);
304  if (!contents) {
305    LOG(WARNING) << "Notification needs an image but has no WebContents";
306    return;
307  }
308
309  contents->DownloadFavicon(
310      url,
311      false,
312      size,
313      base::Bind(
314          &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
315          AsWeakPtr(),
316          callback));
317}
318
319void MessageCenterNotificationManager::ImageDownloads::StartDownloadByKey(
320    const Notification& notification,
321    const char* key,
322    int size,
323    const SetImageCallback& callback) {
324  const base::DictionaryValue* optional_fields = notification.optional_fields();
325  if (optional_fields && optional_fields->HasKey(key)) {
326    string16 url;
327    optional_fields->GetString(key, &url);
328    StartDownloadWithImage(notification, NULL, GURL(url), size, callback);
329  }
330}
331
332void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
333    const SetImageCallback& callback,
334    int download_id,
335    const GURL& image_url,
336    int requested_size,
337    const std::vector<SkBitmap>& bitmaps) {
338  if (bitmaps.empty())
339    return;
340  gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
341  callback.Run(image);
342}
343
344////////////////////////////////////////////////////////////////////////////////
345// ProfileNotification
346
347MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
348    Profile* profile,
349    const Notification& notification,
350    message_center::MessageCenter* message_center)
351    : profile_(profile),
352      notification_(notification),
353      downloads_(new ImageDownloads(message_center)) {
354  DCHECK(profile);
355}
356
357MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
358}
359
360void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
361  downloads_->StartDownloads(notification_);
362}
363
364std::string
365    MessageCenterNotificationManager::ProfileNotification::GetExtensionId() {
366  const ExtensionURLInfo url(notification().origin_url());
367  const ExtensionService* service = profile()->GetExtensionService();
368  const extensions::Extension* extension =
369      service->extensions()->GetExtensionOrAppByURL(url);
370  return extension ? extension->id() : std::string();
371}
372
373////////////////////////////////////////////////////////////////////////////////
374// private
375
376void MessageCenterNotificationManager::AddProfileNotification(
377    ProfileNotification* profile_notification) {
378  const Notification& notification = profile_notification->notification();
379  std::string id = notification.notification_id();
380  // Notification ids should be unique.
381  DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
382  profile_notifications_[id] = profile_notification;
383
384  message_center_->AddNotification(notification.type(),
385                                   notification.notification_id(),
386                                   notification.title(),
387                                   notification.body(),
388                                   notification.display_source(),
389                                   profile_notification->GetExtensionId(),
390                                   notification.optional_fields());
391  profile_notification->StartDownloads();
392  notification.Display();
393}
394
395void MessageCenterNotificationManager::RemoveProfileNotification(
396    ProfileNotification* profile_notification,
397    bool by_user) {
398  profile_notification->notification().Close(by_user);
399  std::string id = profile_notification->notification().notification_id();
400  message_center_->RemoveNotification(id);
401  profile_notifications_.erase(id);
402  delete profile_notification;
403}
404
405MessageCenterNotificationManager::ProfileNotification*
406    MessageCenterNotificationManager::FindProfileNotification(
407        const std::string& id) const {
408  NotificationMap::const_iterator iter = profile_notifications_.find(id);
409  // If the notification is shown in UI, it must be in the map.
410  DCHECK(iter != profile_notifications_.end());
411  DCHECK((*iter).second);
412  return (*iter).second;
413}
414