blob: 6983abdf67ad6df98ced8a9e27c5127dab8aa0ae [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h"
#include <algorithm>
#include <map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/notifications/scheduler/internal/icon_store.h"
#include "chrome/browser/notifications/scheduler/internal/notification_entry.h"
#include "chrome/browser/notifications/scheduler/internal/scheduler_config.h"
#include "chrome/browser/notifications/scheduler/internal/scheduler_utils.h"
#include "chrome/browser/notifications/scheduler/internal/stats.h"
#include "chrome/browser/notifications/scheduler/public/notification_params.h"
#include "chrome/browser/notifications/scheduler/public/notification_scheduler_constant.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
namespace notifications {
namespace {
// Comparator used to sort notification entries based on creation time.
bool CreateTimeCompare(const NotificationEntry* lhs,
const NotificationEntry* rhs) {
DCHECK(lhs && rhs);
return lhs->create_time < rhs->create_time;
}
// Vailidates notification entry. Returns false if the entry should be deleted.
bool ValidateNotificationEntry(const NotificationEntry& entry) {
// Check the deliver time window.
return (entry.schedule_params.deliver_time_start.has_value() &&
entry.schedule_params.deliver_time_end.has_value() &&
entry.schedule_params.deliver_time_end > base::Time::Now() &&
entry.schedule_params.deliver_time_end >=
entry.schedule_params.deliver_time_start);
}
class ScheduledNotificationManagerImpl : public ScheduledNotificationManager {
public:
using NotificationStore = std::unique_ptr<CollectionStore<NotificationEntry>>;
ScheduledNotificationManagerImpl(
NotificationStore notification_store,
std::unique_ptr<IconStore> icon_store,
const std::vector<SchedulerClientType>& clients,
const SchedulerConfig& config)
: notification_store_(std::move(notification_store)),
icon_store_(std::move(icon_store)),
clients_(clients.begin(), clients.end()),
config_(config) {}
ScheduledNotificationManagerImpl(const ScheduledNotificationManagerImpl&) =
delete;
ScheduledNotificationManagerImpl& operator=(
const ScheduledNotificationManagerImpl&) = delete;
private:
// NotificationManager implementation.
void Init(InitCallback callback) override {
icon_store_->InitAndLoadKeys(base::BindOnce(
&ScheduledNotificationManagerImpl::OnIconStoreInitialized,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ScheduleNotification(
std::unique_ptr<NotificationParams> notification_params,
ScheduleCallback callback) override {
DCHECK(notification_params);
std::string guid = notification_params->guid;
DCHECK(!guid.empty());
auto type = notification_params->type;
stats::LogNotificationLifeCycleEvent(
stats::NotificationLifeCycleEvent::kScheduleRequest, type);
if (!clients_.count(type) ||
(notifications_.count(type) && notifications_[type].count(guid))) {
// TODO(xingliu): Report duplicate guid failure.
std::move(callback).Run(false);
return;
}
bool valid = ValidateNotificationParams(*notification_params);
DCHECK(valid) << "Invalid notification parameters.";
if (!valid) {
stats::LogNotificationLifeCycleEvent(
stats::NotificationLifeCycleEvent::kInvalidInput, type);
std::move(callback).Run(false);
return;
}
if (notification_params->enable_ihnr_buttons) {
CreateInhrButtonsPair(&notification_params->notification_data.buttons);
}
auto entry =
std::make_unique<NotificationEntry>(notification_params->type, guid);
auto icon_bundles = std::move(notification_params->notification_data.icons);
entry->notification_data =
std::move(notification_params->notification_data);
entry->schedule_params = std::move(notification_params->schedule_params);
icon_store_->AddIcons(
std::move(icon_bundles),
base::BindOnce(&ScheduledNotificationManagerImpl::OnIconsAdded,
weak_ptr_factory_.GetWeakPtr(), std::move(entry),
std::move(callback)));
}
void DisplayNotification(const std::string& guid,
DisplayCallback callback) override {
NotificationEntry* entry = nullptr;
for (auto it = notifications_.begin(); it != notifications_.end(); it++) {
if (it->second.count(guid)) {
entry = it->second[guid].get();
break;
}
}
if (!entry) {
std::move(callback).Run(nullptr);
return;
}
std::vector<std::string> keys;
for (const auto& pair : entry->icons_uuid) {
keys.emplace_back(pair.second);
}
icon_store_->LoadIcons(
std::move(keys),
base::BindOnce(&ScheduledNotificationManagerImpl::OnIconsLoaded,
weak_ptr_factory_.GetWeakPtr(), entry->type, entry->guid,
std::move(callback)));
}
void GetAllNotifications(Notifications* notifications) const override {
DCHECK(notifications);
notifications->clear();
for (auto it = notifications_.begin(); it != notifications_.end(); it++) {
auto type = it->first;
for (const auto& pair : it->second) {
(*notifications)[type].emplace_back(pair.second.get());
}
}
// Sort by creation time for each notification type.
for (auto it = notifications->begin(); it != notifications->end(); ++it) {
std::sort(it->second.begin(), it->second.end(), &CreateTimeCompare);
}
}
void GetNotifications(
SchedulerClientType type,
std::vector<const NotificationEntry*>* notifications) const override {
DCHECK(notifications);
notifications->clear();
const auto it = notifications_.find(type);
if (it == notifications_.end())
return;
for (const auto& pair : it->second)
notifications->emplace_back(pair.second.get());
}
void DeleteNotifications(SchedulerClientType type) override {
if (!notifications_.count(type))
return;
auto it = notifications_[type].begin();
while (it != notifications_[type].end()) {
const auto& entry = *it->second;
++it;
DeleteNotification(entry, false /*should_delete_in_memory*/);
}
notifications_.erase(type);
}
void OnIconStoreInitialized(InitCallback callback,
bool success,
IconStore::LoadedIconKeys loaded_keys) {
stats::LogDbInit(stats::DatabaseType::kIconDb, success,
loaded_keys ? loaded_keys->size() : 0);
if (!success) {
std::move(callback).Run(false);
return;
}
notification_store_->InitAndLoad(base::BindOnce(
&ScheduledNotificationManagerImpl::OnNotificationStoreInitialized,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(loaded_keys)));
}
void OnNotificationStoreInitialized(
InitCallback callback,
std::unique_ptr<std::vector<std::string>> loaded_icon_keys,
bool success,
CollectionStore<NotificationEntry>::Entries entries) {
stats::LogDbInit(stats::DatabaseType::kNotificationDb, success,
entries.size());
if (!success) {
std::move(callback).Run(false);
return;
}
FilterNotificationEntries(std::move(entries));
FilterIconEntries(std::move(loaded_icon_keys));
std::move(callback).Run(true);
}
void FilterIconEntries(
std::unique_ptr<std::vector<std::string>> uuids_from_icon_store) {
std::unordered_set<std::string> icons_uuid_from_entries;
for (const auto& client_pair : notifications_) {
for (const auto& notification : client_pair.second) {
for (const auto& icon : notification.second->icons_uuid) {
icons_uuid_from_entries.emplace(icon.second);
}
}
}
std::vector<std::string> icons_to_delete;
for (const auto& loaded_icon_key : *uuids_from_icon_store.get()) {
if (!base::Contains(icons_uuid_from_entries, loaded_icon_key)) {
icons_to_delete.emplace_back(loaded_icon_key);
}
}
icon_store_->DeleteIcons(
icons_to_delete,
base::BindOnce(&ScheduledNotificationManagerImpl::OnIconDeleted,
weak_ptr_factory_.GetWeakPtr()));
}
// Filters and loads notification into memory.
void FilterNotificationEntries(
CollectionStore<NotificationEntry>::Entries entries) {
for (auto it = entries.begin(); it != entries.end(); it++) {
auto* entry = it->get();
// Prune expired notifications. Also delete them in db.
bool expired = entry->create_time + config_->notification_expiration <=
base::Time::Now();
bool valid = ValidateNotificationEntry(*entry);
bool deprecated_client = !base::Contains(clients_, entry->type);
if (expired || deprecated_client || !valid) {
DeleteNotification(*entry, false /*should_delete_in_memory*/);
} else {
notifications_[entry->type].emplace(entry->guid, std::move(*it));
}
}
}
void OnIconsAdded(std::unique_ptr<NotificationEntry> entry,
ScheduleCallback schedule_callback,
IconStore::IconTypeUuidMap icons_uuid_map,
bool success) {
stats::LogDbOperation(stats::DatabaseType::kIconDb, success);
if (!success) {
std::move(schedule_callback).Run(false);
return;
}
entry->icons_uuid = std::move(icons_uuid_map);
const auto* entry_ptr = entry.get();
notification_store_->Add(
entry_ptr->guid, *entry_ptr,
base::BindOnce(&ScheduledNotificationManagerImpl::OnNotificationAdded,
weak_ptr_factory_.GetWeakPtr(), std::move(entry),
std::move(schedule_callback)));
}
void OnNotificationAdded(std::unique_ptr<NotificationEntry> entry,
ScheduleCallback schedule_callback,
bool success) {
stats::LogDbOperation(stats::DatabaseType::kNotificationDb, success);
// Delete the icons when failed to add to notification database.
if (!success) {
std::vector<std::string> icons_to_delete;
for (const auto& uuid : entry->icons_uuid) {
icons_to_delete.emplace_back(uuid.second);
}
icon_store_->DeleteIcons(
std::move(icons_to_delete),
base::BindOnce(&ScheduledNotificationManagerImpl::OnIconDeleted,
weak_ptr_factory_.GetWeakPtr()));
std::move(schedule_callback).Run(false);
return;
}
auto type = entry->type;
auto guid = entry->guid;
notifications_[type][guid] = std::move(entry);
stats::LogNotificationLifeCycleEvent(
stats::NotificationLifeCycleEvent::kScheduled, type);
std::move(schedule_callback).Run(true);
}
void OnNotificationDeleted(bool success) {
stats::LogDbOperation(stats::DatabaseType::kNotificationDb, success);
}
void OnIconDeleted(bool success) {
stats::LogDbOperation(stats::DatabaseType::kIconDb, success);
}
void OnIconsLoaded(SchedulerClientType client_type,
const std::string& guid,
DisplayCallback display_callback,
bool success,
IconStore::LoadedIconsMap loaded_icons_map) {
stats::LogDbOperation(stats::DatabaseType::kIconDb, success);
auto* entry_ptr = FindNotificationEntry(client_type, guid);
if (!entry_ptr) {
std::move(display_callback).Run(nullptr);
return;
}
if (!success) {
DeleteNotification(*entry_ptr, true /*should_delete_in_memory*/);
std::move(display_callback).Run(nullptr);
return;
}
// Glue the icon data to entry.
std::unique_ptr<NotificationEntry> entry =
std::move(notifications_[client_type][guid]);
DCHECK(entry);
for (const auto& pair : entry->icons_uuid) {
auto icon_bundle = IconBundle(std::move(loaded_icons_map[pair.second]));
entry->notification_data.icons.emplace(pair.first,
std::move(icon_bundle));
}
// Before moving out the entry, delete it from container and disk.
DeleteNotification(*entry.get(), true /*should_delete_in_memory*/);
std::move(display_callback).Run(std::move(entry));
}
NotificationEntry* FindNotificationEntry(SchedulerClientType type,
const std::string& guid) {
if (!notifications_.count(type) || !notifications_[type].count(guid))
return nullptr;
return notifications_[type][guid].get();
}
// Delete NotitificationEntry from memory and disk.
void DeleteNotification(const NotificationEntry& entry,
bool should_delete_in_memory) {
// Deletes icon first.
std::vector<std::string> icons_to_delete;
for (const auto& icon_id : entry.icons_uuid) {
icons_to_delete.emplace_back(icon_id.second);
}
icon_store_->DeleteIcons(
std::move(icons_to_delete),
base::BindOnce(&ScheduledNotificationManagerImpl::OnIconDeleted,
weak_ptr_factory_.GetWeakPtr()));
auto guid = entry.guid;
auto type = entry.type;
// Deletes notification entry.
notification_store_->Delete(
guid,
base::BindOnce(&ScheduledNotificationManagerImpl::OnNotificationDeleted,
weak_ptr_factory_.GetWeakPtr()));
if (should_delete_in_memory) {
notifications_[type].erase(guid);
if (notifications_[type].empty())
notifications_.erase(type);
}
}
// Create two default buttons {Helpful, Unhelpful} for notification.
void CreateInhrButtonsPair(std::vector<NotificationData::Button>* buttons) {
buttons->clear();
NotificationData::Button helpful_button;
helpful_button.type = ActionButtonType::kHelpful;
helpful_button.id = notifications::kDefaultHelpfulButtonId;
helpful_button.text =
l10n_util::GetStringUTF16(IDS_NOTIFICATION_DEFAULT_HELPFUL_BUTTON_TEXT);
buttons->emplace_back(std::move(helpful_button));
NotificationData::Button unhelpful_button;
unhelpful_button.type = ActionButtonType::kUnhelpful;
unhelpful_button.id = notifications::kDefaultUnhelpfulButtonId;
unhelpful_button.text = l10n_util::GetStringUTF16(
IDS_NOTIFICATION_DEFAULT_UNHELPFUL_BUTTON_TEXT);
buttons->emplace_back(std::move(unhelpful_button));
}
// Vailidates notification parameters. Returns false if the parameters are
// invalid.
bool ValidateNotificationParams(const NotificationParams& params) {
// Validate time window. Currently we only support deliver notification
// according to a time window, the deliver window should before the
// expiration duration of notification data in config.
if (!params.schedule_params.deliver_time_start.has_value() ||
!params.schedule_params.deliver_time_end.has_value() ||
params.schedule_params.deliver_time_start.value() >
params.schedule_params.deliver_time_end.value() ||
params.schedule_params.deliver_time_end.value() - base::Time::Now() >=
config_->notification_expiration) {
return false;
}
// Validate ihnr buttons option. Custom buttons will be overwritten.
if (params.enable_ihnr_buttons &&
!params.notification_data.buttons.empty()) {
return false;
}
// Validate icon bundle data is correct: icon resource id should never be
// persisted to disk,since it can change in different versions. Client
// should overwrite with Android resource id in BeforeShowNotification
// callback if it is required.
for (const auto& icon_bundle_map : params.notification_data.icons) {
const auto& icon_bundle = icon_bundle_map.second;
if (icon_bundle.resource_id)
return false;
}
return true;
}
NotificationStore notification_store_;
std::unique_ptr<IconStore> icon_store_;
const std::unordered_set<SchedulerClientType> clients_;
std::map<SchedulerClientType,
std::map<std::string, std::unique_ptr<NotificationEntry>>>
notifications_;
const raw_ref<const SchedulerConfig, DanglingUntriaged> config_;
base::WeakPtrFactory<ScheduledNotificationManagerImpl> weak_ptr_factory_{
this};
};
} // namespace
// static
std::unique_ptr<ScheduledNotificationManager>
ScheduledNotificationManager::Create(
std::unique_ptr<CollectionStore<NotificationEntry>> notification_store,
std::unique_ptr<IconStore> icon_store,
const std::vector<SchedulerClientType>& clients,
const SchedulerConfig& config) {
return std::make_unique<ScheduledNotificationManagerImpl>(
std::move(notification_store), std::move(icon_store), clients, config);
}
ScheduledNotificationManager::ScheduledNotificationManager() = default;
ScheduledNotificationManager::~ScheduledNotificationManager() = default;
} // namespace notifications