blob: eb40715bb1a2b1d001a8f45854a23cafa41a50ba [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/notifications/notification_ui_manager_impl.h"
#include <memory>
#include <utility>
#include "base/logging.h"
#include "build/build_config.h"
#include "chrome/browser/notifications/fullscreen_notification_blocker.h"
#include "chrome/browser/notifications/popups_only_ui_controller.h"
#include "chrome/browser/notifications/profile_notification.h"
#include "chrome/browser/notifications/screen_lock_notification_blocker.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_types.h"
#include "ui/message_center/public/cpp/message_center_constants.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notifier_id.h"
using message_center::MessageCenter;
using message_center::NotifierId;
// static
std::unique_ptr<NotificationUIManager> NotificationUIManager::Create() {
// If there's no MessageCenter, there should be no NotificationUIManager to
// manage it.
if (!message_center::MessageCenter::Get())
return nullptr;
return std::make_unique<NotificationUIManagerImpl>();
}
NotificationUIManagerImpl::NotificationUIManagerImpl()
: system_observer_(this),
popups_only_ui_controller_(std::make_unique<PopupsOnlyUiController>()) {
auto* message_center = MessageCenter::Get();
message_center->AddObserver(this);
blockers_.push_back(
std::make_unique<ScreenLockNotificationBlocker>(message_center));
blockers_.push_back(
std::make_unique<FullscreenNotificationBlocker>(message_center));
}
NotificationUIManagerImpl::~NotificationUIManagerImpl() {
// The message center may have already been shut down (on Chrome OS).
if (MessageCenter::Get())
MessageCenter::Get()->RemoveObserver(this);
profile_notifications_.clear();
}
////////////////////////////////////////////////////////////////////////////////
// NotificationUIManager
void NotificationUIManagerImpl::Add(
const message_center::Notification& notification,
Profile* profile) {
// We won't have time to process and act on this notification.
if (is_shutdown_started_)
return;
if (Update(notification, profile))
return;
auto profile_notification_ptr =
std::make_unique<ProfileNotification>(profile, notification);
ProfileNotification* profile_notification = profile_notification_ptr.get();
// WARNING: You MUST use AddProfileNotification or update the message center
// via the notification within a ProfileNotification object or the profile ID
// will not be correctly set for ChromeOS.
// Takes ownership of profile_notification.
AddProfileNotification(std::move(profile_notification_ptr));
MessageCenter::Get()->AddNotification(
std::make_unique<message_center::Notification>(
profile_notification->notification()));
}
bool NotificationUIManagerImpl::Update(
const message_center::Notification& notification,
Profile* profile) {
const std::string profile_id = ProfileNotification::GetProfileNotificationId(
notification.id(), NotificationUIManager::GetProfileID(profile));
for (auto iter = profile_notifications_.begin();
iter != profile_notifications_.end(); ++iter) {
ProfileNotification* old_notification = (*iter).second.get();
if (old_notification->notification().id() != profile_id)
continue;
// The ID should uniquely identify the notification, but as a sanity check
// make sure we got the right origin URL and profile.
DCHECK_EQ(old_notification->notification().origin_url(),
notification.origin_url());
DCHECK_EQ(old_notification->profile_id(),
NotificationUIManager::GetProfileID(profile));
// Changing the type from non-progress to progress does not count towards
// the immediate update allowed in the message center.
std::string old_id = old_notification->notification().id();
// Add/remove notification in the local list but just update the same
// one in MessageCenter.
auto new_notification =
std::make_unique<ProfileNotification>(profile, notification);
const message_center::Notification& notification =
new_notification->notification();
// Delete the old one after the new one is created to ensure we don't run
// out of KeepAlives.
profile_notifications_.erase(old_id);
profile_notifications_[notification.id()] = std::move(new_notification);
// TODO(liyanhou): Add routing updated notifications to alternative
// providers.
// WARNING: You MUST use AddProfileNotification or update the message
// center via the notification within a ProfileNotification object or the
// profile ID will not be correctly set for ChromeOS.
MessageCenter::Get()->UpdateNotification(
old_id, std::make_unique<message_center::Notification>(notification));
return true;
}
return false;
}
const message_center::Notification* NotificationUIManagerImpl::FindById(
const std::string& id,
ProfileID profile_id) const {
std::string profile_notification_id =
ProfileNotification::GetProfileNotificationId(id, profile_id);
auto iter = profile_notifications_.find(profile_notification_id);
if (iter == profile_notifications_.end())
return nullptr;
return &(iter->second->notification());
}
bool NotificationUIManagerImpl::CancelById(const std::string& id,
ProfileID profile_id) {
std::string profile_notification_id =
ProfileNotification::GetProfileNotificationId(id, profile_id);
// See if this ID hasn't been shown yet.
// If it has been shown, remove it.
auto iter = profile_notifications_.find(profile_notification_id);
if (iter == profile_notifications_.end())
return false;
RemoveProfileNotification(iter->first);
MessageCenter::Get()->RemoveNotification(profile_notification_id,
/* by_user */ false);
return true;
}
std::set<std::string> NotificationUIManagerImpl::GetAllIdsByProfile(
ProfileID profile_id) {
std::set<std::string> original_ids;
for (const auto& pair : profile_notifications_) {
if (pair.second->profile_id() == profile_id)
original_ids.insert(pair.second->original_id());
}
return original_ids;
}
bool NotificationUIManagerImpl::CancelAllBySourceOrigin(const GURL& source) {
// Same pattern as CancelById, but more complicated than the above
// because there may be multiple notifications from the same source.
bool removed = false;
for (auto loopiter = profile_notifications_.begin();
loopiter != profile_notifications_.end();) {
auto curiter = loopiter++;
if ((*curiter).second->notification().origin_url() == source) {
const std::string id = curiter->first;
RemoveProfileNotification(id);
MessageCenter::Get()->RemoveNotification(id, /* by_user */ false);
removed = true;
}
}
return removed;
}
bool NotificationUIManagerImpl::CancelAllByProfile(ProfileID profile_id) {
// Same pattern as CancelAllBySourceOrigin.
bool removed = false;
for (auto loopiter = profile_notifications_.begin();
loopiter != profile_notifications_.end();) {
auto curiter = loopiter++;
if (profile_id == (*curiter).second->profile_id()) {
const std::string id = curiter->first;
RemoveProfileNotification(id);
MessageCenter::Get()->RemoveNotification(id, /* by_user */ false);
removed = true;
}
}
return removed;
}
void NotificationUIManagerImpl::CancelAll() {
MessageCenter::Get()->RemoveAllNotifications(
false /* by_user */, message_center::MessageCenter::RemoveType::ALL);
}
void NotificationUIManagerImpl::StartShutdown() {
is_shutdown_started_ = true;
CancelAll();
popups_only_ui_controller_.reset();
}
////////////////////////////////////////////////////////////////////////////////
// MessageCenter::Observer
void NotificationUIManagerImpl::OnNotificationRemoved(const std::string& id,
bool by_user) {
RemoveProfileNotification(id);
}
void NotificationUIManagerImpl::ResetUiControllerForTest() {
popups_only_ui_controller_.reset();
}
std::string NotificationUIManagerImpl::GetMessageCenterNotificationIdForTest(
const std::string& id,
Profile* profile) {
return ProfileNotification::GetProfileNotificationId(id,
GetProfileID(profile));
}
////////////////////////////////////////////////////////////////////////////////
// private
void NotificationUIManagerImpl::AddProfileNotification(
std::unique_ptr<ProfileNotification> profile_notification) {
const message_center::Notification& notification =
profile_notification->notification();
std::string id = notification.id();
// Notification ids should be unique.
DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
profile_notifications_[id] = std::move(profile_notification);
}
void NotificationUIManagerImpl::RemoveProfileNotification(
const std::string& notification_id) {
auto it = profile_notifications_.find(notification_id);
if (it == profile_notifications_.end())
return;
// Delay destruction of the ProfileNotification until current task is
// completed. This must be done because this ProfileNotification might have
// the one ScopedKeepAlive object that was keeping the browser alive, and
// destroying it would result in:
// a) A reentrant call to this class. Because every method in this class
// touches |profile_notifications_|, |profile_notifications_| must always
// be in a self-consistent state in moments where re-entrance might happen.
// b) A crash like https://crbug.com/649971 because it can trigger
// shutdown process while we're still inside the call stack from UI
// framework.
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
it->second.release());
profile_notifications_.erase(it);
}
ProfileNotification* NotificationUIManagerImpl::FindProfileNotification(
const std::string& id) const {
auto iter = profile_notifications_.find(id);
if (iter == profile_notifications_.end())
return nullptr;
return (*iter).second.get();
}