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_registry_simple.h"
10#include "base/prefs/pref_service.h"
11#include "base/stl_util.h"
12#include "chrome/browser/extensions/api/notification_provider/notification_provider_api.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/extension_welcome_notification.h"
16#include "chrome/browser/notifications/extension_welcome_notification_factory.h"
17#include "chrome/browser/notifications/fullscreen_notification_blocker.h"
18#include "chrome/browser/notifications/message_center_settings_controller.h"
19#include "chrome/browser/notifications/notification.h"
20#include "chrome/browser/notifications/notification_conversion_helper.h"
21#include "chrome/browser/notifications/screen_lock_notification_blocker.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/common/extensions/api/notification_provider.h"
24#include "chrome/common/pref_names.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/common/url_constants.h"
27#include "extensions/browser/extension_registry.h"
28#include "extensions/common/extension_set.h"
29#include "extensions/common/permissions/permissions_data.h"
30#include "ui/gfx/image/image_skia.h"
31#include "ui/message_center/message_center_style.h"
32#include "ui/message_center/message_center_tray.h"
33#include "ui/message_center/message_center_types.h"
34#include "ui/message_center/notifier_settings.h"
35
36#if defined(OS_CHROMEOS)
37#include "chrome/browser/notifications/login_state_notification_blocker_chromeos.h"
38#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
39#endif
40
41#if defined(USE_ASH)
42#include "ash/shell.h"
43#include "ash/system/web_notification/web_notification_tray.h"
44#endif
45
46#if defined(OS_WIN)
47// The first-run balloon will be shown |kFirstRunIdleDelaySeconds| after all
48// popups go away and the user has notifications in the message center.
49const int kFirstRunIdleDelaySeconds = 1;
50#endif
51
52MessageCenterNotificationManager::MessageCenterNotificationManager(
53    message_center::MessageCenter* message_center,
54    PrefService* local_state,
55    scoped_ptr<message_center::NotifierSettingsProvider> settings_provider)
56    : message_center_(message_center),
57#if defined(OS_WIN)
58      first_run_idle_timeout_(
59          base::TimeDelta::FromSeconds(kFirstRunIdleDelaySeconds)),
60      weak_factory_(this),
61#endif
62      settings_provider_(settings_provider.Pass()),
63      system_observer_(this),
64      stats_collector_(message_center),
65      google_now_stats_collector_(message_center) {
66#if defined(OS_WIN)
67  first_run_pref_.Init(prefs::kMessageCenterShowedFirstRunBalloon, local_state);
68#endif
69
70  message_center_->AddObserver(this);
71  message_center_->SetNotifierSettingsProvider(settings_provider_.get());
72
73#if defined(OS_CHROMEOS)
74#if !defined(USE_ATHENA)
75  // TODO(oshima|hashimoto): Support notification on athena. crbug.com/408755.
76  blockers_.push_back(
77      new LoginStateNotificationBlockerChromeOS(message_center));
78#endif
79#else
80  blockers_.push_back(new ScreenLockNotificationBlocker(message_center));
81#endif
82  blockers_.push_back(new FullscreenNotificationBlocker(message_center));
83
84#if defined(OS_WIN) || defined(OS_MACOSX) \
85  || (defined(OS_LINUX) && !defined(OS_CHROMEOS))
86  // On Windows, Linux and Mac, the notification manager owns the tray icon and
87  // views.Other platforms have global ownership and Create will return NULL.
88  tray_.reset(message_center::CreateMessageCenterTray());
89#endif
90}
91
92MessageCenterNotificationManager::~MessageCenterNotificationManager() {
93  message_center_->SetNotifierSettingsProvider(NULL);
94  message_center_->RemoveObserver(this);
95
96  STLDeleteContainerPairSecondPointers(profile_notifications_.begin(),
97                                       profile_notifications_.end());
98  profile_notifications_.clear();
99}
100
101void MessageCenterNotificationManager::RegisterPrefs(
102    PrefRegistrySimple* registry) {
103  registry->RegisterBooleanPref(prefs::kMessageCenterShowedFirstRunBalloon,
104                                false);
105  registry->RegisterBooleanPref(prefs::kMessageCenterShowIcon, true);
106  registry->RegisterBooleanPref(prefs::kMessageCenterForcedOnTaskbar, false);
107}
108
109////////////////////////////////////////////////////////////////////////////////
110// NotificationUIManager
111
112void MessageCenterNotificationManager::Add(const Notification& notification,
113                                           Profile* profile) {
114  if (Update(notification, profile))
115    return;
116
117  ExtensionWelcomeNotificationFactory::GetForBrowserContext(profile)->
118      ShowWelcomeNotificationIfNecessary(notification);
119
120  // WARNING: You MUST update the message center via the notification within a
121  // ProfileNotification object or the profile ID will not be correctly set for
122  // ChromeOS.
123  ProfileNotification* profile_notification(
124      new ProfileNotification(profile, notification, message_center_));
125  AddProfileNotification(profile_notification);
126
127  // TODO(liyanhou): Change the logic to only send notifications to one party.
128  // Currently, if there is an app with notificationProvider permission,
129  // notifications will go to both message center and the app.
130  // Change it to send notifications to message center only when the user chose
131  // default message center (extension_id.empty()).
132
133  // If there exist apps/extensions that have notificationProvider permission,
134  // route notifications to one of the apps/extensions.
135  std::string extension_id = GetExtensionTakingOverNotifications(profile);
136  if (!extension_id.empty())
137    profile_notification->AddToAlternateProvider(extension_id);
138
139  message_center_->AddNotification(make_scoped_ptr(
140      new message_center::Notification(profile_notification->notification())));
141  profile_notification->StartDownloads();
142}
143
144bool MessageCenterNotificationManager::Update(const Notification& notification,
145                                              Profile* profile) {
146  const base::string16& replace_id = notification.replace_id();
147  if (replace_id.empty())
148    return false;
149
150  const GURL origin_url = notification.origin_url();
151  DCHECK(origin_url.is_valid());
152
153  // Since replace_id is provided by arbitrary JS, we need to use origin_url
154  // (which is an app url in case of app/extension) to scope the replace ids
155  // in the given profile.
156  for (NotificationMap::iterator iter = profile_notifications_.begin();
157       iter != profile_notifications_.end(); ++iter) {
158    ProfileNotification* old_notification = (*iter).second;
159    if (old_notification->notification().replace_id() == replace_id &&
160        old_notification->notification().origin_url() == origin_url &&
161        old_notification->profile() == profile) {
162      // Changing the type from non-progress to progress does not count towards
163      // the immediate update allowed in the message center.
164      std::string old_id =
165          old_notification->notification().delegate_id();
166
167      // Add/remove notification in the local list but just update the same
168      // one in MessageCenter.
169      delete old_notification;
170      profile_notifications_.erase(old_id);
171      ProfileNotification* new_notification =
172          new ProfileNotification(profile, notification, message_center_);
173      profile_notifications_[notification.delegate_id()] = new_notification;
174
175      // TODO(liyanhou): Add routing updated notifications to alternative
176      // providers.
177
178      // WARNING: You MUST use AddProfileNotification or update the message
179      // center via the notification within a ProfileNotification object or the
180      // profile ID will not be correctly set for ChromeOS.
181      message_center_->UpdateNotification(
182          old_id,
183          make_scoped_ptr(new message_center::Notification(
184              new_notification->notification())));
185
186      new_notification->StartDownloads();
187      return true;
188    }
189  }
190  return false;
191}
192
193const Notification* MessageCenterNotificationManager::FindById(
194    const std::string& id) const {
195  NotificationMap::const_iterator iter = profile_notifications_.find(id);
196  if (iter == profile_notifications_.end())
197    return NULL;
198  return &(iter->second->notification());
199}
200
201bool MessageCenterNotificationManager::CancelById(const std::string& id) {
202  // See if this ID hasn't been shown yet.
203  // If it has been shown, remove it.
204  NotificationMap::iterator iter = profile_notifications_.find(id);
205  if (iter == profile_notifications_.end())
206    return false;
207
208  RemoveProfileNotification(iter->second);
209  message_center_->RemoveNotification(id, /* by_user */ false);
210  return true;
211}
212
213std::set<std::string>
214MessageCenterNotificationManager::GetAllIdsByProfileAndSourceOrigin(
215    Profile* profile,
216    const GURL& source) {
217
218  std::set<std::string> notification_ids;
219  for (NotificationMap::iterator iter = profile_notifications_.begin();
220       iter != profile_notifications_.end(); iter++) {
221    if ((*iter).second->notification().origin_url() == source &&
222        profile == (*iter).second->profile()) {
223      notification_ids.insert(iter->first);
224    }
225  }
226  return notification_ids;
227}
228
229bool MessageCenterNotificationManager::CancelAllBySourceOrigin(
230    const GURL& source) {
231  // Same pattern as CancelById, but more complicated than the above
232  // because there may be multiple notifications from the same source.
233  bool removed = false;
234
235  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
236       loopiter != profile_notifications_.end(); ) {
237    NotificationMap::iterator curiter = loopiter++;
238    if ((*curiter).second->notification().origin_url() == source) {
239      const std::string id = curiter->first;
240      RemoveProfileNotification(curiter->second);
241      message_center_->RemoveNotification(id, /* by_user */ false);
242      removed = true;
243    }
244  }
245  return removed;
246}
247
248bool MessageCenterNotificationManager::CancelAllByProfile(Profile* profile) {
249  // Same pattern as CancelAllBySourceOrigin.
250  bool removed = false;
251
252  for (NotificationMap::iterator loopiter = profile_notifications_.begin();
253       loopiter != profile_notifications_.end(); ) {
254    NotificationMap::iterator curiter = loopiter++;
255    if (profile == (*curiter).second->profile()) {
256      const std::string id = curiter->first;
257      RemoveProfileNotification(curiter->second);
258      message_center_->RemoveNotification(id, /* by_user */ false);
259      removed = true;
260    }
261  }
262  return removed;
263}
264
265void MessageCenterNotificationManager::CancelAll() {
266  message_center_->RemoveAllNotifications(/* by_user */ false);
267}
268
269////////////////////////////////////////////////////////////////////////////////
270// MessageCenter::Observer
271void MessageCenterNotificationManager::OnNotificationRemoved(
272    const std::string& notification_id,
273    bool by_user) {
274  NotificationMap::const_iterator iter =
275      profile_notifications_.find(notification_id);
276  if (iter != profile_notifications_.end())
277    RemoveProfileNotification(iter->second);
278
279#if defined(OS_WIN)
280  CheckFirstRunTimer();
281#endif
282}
283
284void MessageCenterNotificationManager::OnCenterVisibilityChanged(
285    message_center::Visibility visibility) {
286#if defined(OS_WIN)
287  if (visibility == message_center::VISIBILITY_TRANSIENT)
288    CheckFirstRunTimer();
289#endif
290}
291
292void MessageCenterNotificationManager::OnNotificationUpdated(
293    const std::string& notification_id) {
294#if defined(OS_WIN)
295  CheckFirstRunTimer();
296#endif
297}
298
299void MessageCenterNotificationManager::EnsureMessageCenterClosed() {
300  if (tray_.get())
301    tray_->GetMessageCenterTray()->HideMessageCenterBubble();
302
303#if defined(USE_ASH)
304  if (ash::Shell::HasInstance()) {
305    ash::WebNotificationTray* tray =
306        ash::Shell::GetInstance()->GetWebNotificationTray();
307    if (tray)
308      tray->GetMessageCenterTray()->HideMessageCenterBubble();
309  }
310#endif
311}
312
313void MessageCenterNotificationManager::SetMessageCenterTrayDelegateForTest(
314    message_center::MessageCenterTrayDelegate* delegate) {
315  tray_.reset(delegate);
316}
317
318////////////////////////////////////////////////////////////////////////////////
319// ImageDownloads
320
321MessageCenterNotificationManager::ImageDownloads::ImageDownloads(
322    message_center::MessageCenter* message_center,
323    ImageDownloadsObserver* observer)
324    : message_center_(message_center),
325      pending_downloads_(0),
326      observer_(observer) {
327}
328
329MessageCenterNotificationManager::ImageDownloads::~ImageDownloads() { }
330
331void MessageCenterNotificationManager::ImageDownloads::StartDownloads(
332    const Notification& notification) {
333  // In case all downloads are synchronous, assume a pending download.
334  AddPendingDownload();
335
336  // Notification primary icon.
337  StartDownloadWithImage(
338      notification,
339      &notification.icon(),
340      notification.icon_url(),
341      base::Bind(&message_center::MessageCenter::SetNotificationIcon,
342                 base::Unretained(message_center_),
343                 notification.delegate_id()));
344
345  // Notification image.
346  StartDownloadWithImage(
347      notification,
348      NULL,
349      notification.image_url(),
350      base::Bind(&message_center::MessageCenter::SetNotificationImage,
351                 base::Unretained(message_center_),
352                 notification.delegate_id()));
353
354  // Notification button icons.
355  StartDownloadWithImage(
356      notification,
357      NULL,
358      notification.button_one_icon_url(),
359      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
360                 base::Unretained(message_center_),
361                 notification.delegate_id(),
362                 0));
363  StartDownloadWithImage(
364      notification,
365      NULL,
366      notification.button_two_icon_url(),
367      base::Bind(&message_center::MessageCenter::SetNotificationButtonIcon,
368                 base::Unretained(message_center_),
369                 notification.delegate_id(),
370                 1));
371
372  // This should tell the observer we're done if everything was synchronous.
373  PendingDownloadCompleted();
374}
375
376void MessageCenterNotificationManager::ImageDownloads::StartDownloadWithImage(
377    const Notification& notification,
378    const gfx::Image* image,
379    const GURL& url,
380    const SetImageCallback& callback) {
381  // Set the image directly if we have it.
382  if (image && !image->IsEmpty()) {
383    callback.Run(*image);
384    return;
385  }
386
387  // Leave the image null if there's no URL.
388  if (url.is_empty())
389    return;
390
391  content::WebContents* contents = notification.delegate()->GetWebContents();
392  if (!contents) {
393    LOG(WARNING) << "Notification needs an image but has no WebContents";
394    return;
395  }
396
397  AddPendingDownload();
398
399  contents->DownloadImage(
400      url,
401      false,  // Not a favicon
402      0,  // No maximum size
403      base::Bind(
404          &MessageCenterNotificationManager::ImageDownloads::DownloadComplete,
405          AsWeakPtr(),
406          callback));
407}
408
409void MessageCenterNotificationManager::ImageDownloads::DownloadComplete(
410    const SetImageCallback& callback,
411    int download_id,
412    int http_status_code,
413    const GURL& image_url,
414    const std::vector<SkBitmap>& bitmaps,
415    const std::vector<gfx::Size>& original_bitmap_sizes) {
416  PendingDownloadCompleted();
417
418  if (bitmaps.empty())
419    return;
420  gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmaps[0]);
421  callback.Run(image);
422}
423
424// Private methods.
425
426void MessageCenterNotificationManager::ImageDownloads::AddPendingDownload() {
427  ++pending_downloads_;
428}
429
430void
431MessageCenterNotificationManager::ImageDownloads::PendingDownloadCompleted() {
432  DCHECK(pending_downloads_ > 0);
433  if (--pending_downloads_ == 0 && observer_)
434    observer_->OnDownloadsCompleted();
435}
436
437////////////////////////////////////////////////////////////////////////////////
438// ProfileNotification
439
440MessageCenterNotificationManager::ProfileNotification::ProfileNotification(
441    Profile* profile,
442    const Notification& notification,
443    message_center::MessageCenter* message_center)
444    : profile_(profile),
445      notification_(notification),
446      downloads_(new ImageDownloads(message_center, this)) {
447  DCHECK(profile);
448#if defined(OS_CHROMEOS)
449  notification_.set_profile_id(multi_user_util::GetUserIDFromProfile(profile));
450#endif
451}
452
453MessageCenterNotificationManager::ProfileNotification::~ProfileNotification() {
454}
455
456void MessageCenterNotificationManager::ProfileNotification::StartDownloads() {
457  downloads_->StartDownloads(notification_);
458}
459
460void
461MessageCenterNotificationManager::ProfileNotification::OnDownloadsCompleted() {
462  notification_.delegate()->ReleaseRenderViewHost();
463}
464
465void
466MessageCenterNotificationManager::ProfileNotification::AddToAlternateProvider(
467    const std::string extension_id) {
468  // Convert data from Notification type to NotificationOptions type.
469  extensions::api::notifications::NotificationOptions options;
470  NotificationConversionHelper::NotificationToNotificationOptions(notification_,
471                                                                  &options);
472
473  // Send the notification to the alternate provider extension/app.
474  extensions::NotificationProviderEventRouter event_router(profile_);
475  event_router.CreateNotification(extension_id,
476                                  notification_.notifier_id().id,
477                                  notification_.delegate_id(),
478                                  options);
479}
480
481////////////////////////////////////////////////////////////////////////////////
482// private
483
484void MessageCenterNotificationManager::AddProfileNotification(
485    ProfileNotification* profile_notification) {
486  std::string id = profile_notification->notification().delegate_id();
487  // Notification ids should be unique.
488  DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
489  profile_notifications_[id] = profile_notification;
490}
491
492void MessageCenterNotificationManager::RemoveProfileNotification(
493    ProfileNotification* profile_notification) {
494  std::string id = profile_notification->notification().delegate_id();
495  profile_notifications_.erase(id);
496  delete profile_notification;
497}
498
499MessageCenterNotificationManager::ProfileNotification*
500    MessageCenterNotificationManager::FindProfileNotification(
501        const std::string& id) const {
502  NotificationMap::const_iterator iter = profile_notifications_.find(id);
503  if (iter == profile_notifications_.end())
504    return NULL;
505
506  return (*iter).second;
507}
508
509std::string
510MessageCenterNotificationManager::GetExtensionTakingOverNotifications(
511    Profile* profile) {
512  // TODO(liyanhou): When additional settings in Chrome Settings is implemented,
513  // change choosing the last app with permission to a user selected app.
514  extensions::ExtensionRegistry* registry =
515      extensions::ExtensionRegistry::Get(profile);
516  DCHECK(registry);
517  std::string extension_id;
518  for (extensions::ExtensionSet::const_iterator it =
519           registry->enabled_extensions().begin();
520       it != registry->enabled_extensions().end();
521       ++it) {
522    if ((*it->get()).permissions_data()->HasAPIPermission(
523            extensions::APIPermission::ID::kNotificationProvider)) {
524      extension_id = (*it->get()).id();
525      return extension_id;
526    }
527  }
528  return extension_id;
529}
530