blob: 484f046d4d1f554607eba24b9bd48a45c6c3ac45 [file] [log] [blame]
// Copyright 2019 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/scheduler/internal/display_decider.h"
#include <algorithm>
#include "base/time/clock.h"
#include "chrome/browser/notifications/scheduler/internal/impression_types.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"
using Notifications = notifications::DisplayDecider::Notifications;
using Results = notifications::DisplayDecider::Results;
using ClientStates = notifications::DisplayDecider::ClientStates;
namespace notifications {
namespace {
// Helper class contains the actual logic to decide which notifications to show.
// This is an one-shot class, callers should create a new object each time.
class DecisionHelper {
public:
DecisionHelper(const SchedulerConfig* config,
const std::vector<SchedulerClientType>& clients,
base::Clock* clock,
Notifications notifications,
ClientStates client_states)
: notifications_(std::move(notifications)),
client_states_(std::move(client_states)),
config_(config),
clients_(clients),
clock_(clock),
last_shown_type_(SchedulerClientType::kUnknown),
shown_(0) {}
DecisionHelper(const DecisionHelper&) = delete;
DecisionHelper& operator=(const DecisionHelper&) = delete;
~DecisionHelper() = default;
// Figures out a list of notifications to show.
void DecideNotificationToShow(Results* results) {
NotificationsShownToday(client_states_, &shown_per_type_, &shown_,
&last_shown_type_, clock_);
FilterNotifications();
PickNotificationToShow(results);
}
private:
// Filter notifications based on scheduling parameters.
void FilterNotifications() {
Notifications filtered_notifications;
for (const auto& pair : notifications_) {
// Client under suppression will not have notification to show.
auto it = client_states_.find(pair.first);
if (it != client_states_.end() &&
it->second->suppression_info.has_value()) {
continue;
}
for (const auto* notification : pair.second) {
DCHECK(notification);
DCHECK_NE(notification->schedule_params.priority,
ScheduleParams::Priority::kNoThrottle);
if (!ShouldFilterOut(notification))
filtered_notifications[notification->type].emplace_back(notification);
}
}
notifications_.swap(filtered_notifications);
}
bool ShouldFilterOut(const NotificationEntry* entry) {
base::Time now = clock_->Now();
// Filter with time window. Must have |deliver_time_start|.
if (!entry->schedule_params.deliver_time_start.has_value())
return true;
bool meet_deliver_time_start =
now >= entry->schedule_params.deliver_time_start.value();
DCHECK(entry->schedule_params.deliver_time_end.has_value());
bool meet_deliver_time_end =
entry->schedule_params.deliver_time_end.has_value()
? now <= entry->schedule_params.deliver_time_end.value()
: false;
if (meet_deliver_time_start && meet_deliver_time_end) {
return false;
}
return true;
}
// Picks a list of notifications to show.
void PickNotificationToShow(Results* to_show) {
DCHECK(to_show);
if (shown_ > config_->max_daily_shown_all_type || clients_.empty())
return;
// No previous shown notification, move the iterator to last element.
// We will iterate through all client types later.
auto it = std::find(clients_.begin(), clients_.end(), last_shown_type_);
if (it == clients_.end()) {
DCHECK_EQ(last_shown_type_, SchedulerClientType::kUnknown);
last_shown_type_ = clients_.back();
it = clients_.end() - 1;
}
DCHECK_NE(last_shown_type_, SchedulerClientType::kUnknown);
size_t steps = 0u;
// Circling around all clients to find new notification to show.
do {
// Move the iterator to next client type.
DCHECK(it != clients_.end());
if (++it == clients_.end())
it = clients_.begin();
++steps;
SchedulerClientType type = *it;
// Check quota for all types and current background task type.
if (ReachDailyQuota())
break;
// Check quota for this type, and continue to iterate other types.
if (NoMoreNotificationToShow(type))
continue;
// Show the last notification in the vector. Notice the order depends on
// how the vector is sorted.
to_show->emplace(notifications_[type].back()->guid);
notifications_[type].pop_back();
shown_per_type_[type]++;
shown_++;
steps = 0u;
// Stop if we didn't find anything new to show, and have looped around
// all clients.
} while (steps <= clients_.size());
}
bool NoMoreNotificationToShow(SchedulerClientType type) {
auto it = client_states_.find(type);
int max_daily_show =
it == client_states_.end() ? 0 : it->second->current_max_daily_show;
return notifications_[type].empty() ||
shown_per_type_[type] >= config_->max_daily_shown_per_type ||
shown_per_type_[type] >= max_daily_show;
}
bool ReachDailyQuota() const {
return shown_ >= config_->max_daily_shown_all_type;
}
// Scheduled notifications as candidates to display to the user.
Notifications notifications_;
const ClientStates client_states_;
const SchedulerConfig* config_;
const std::vector<SchedulerClientType> clients_;
base::Clock* clock_;
SchedulerClientType last_shown_type_;
std::map<SchedulerClientType, int> shown_per_type_;
int shown_;
};
class DisplayDeciderImpl : public DisplayDecider {
public:
DisplayDeciderImpl(const SchedulerConfig* config,
std::vector<SchedulerClientType> clients,
base::Clock* clock)
: config_(config), clients_(std::move(clients)), clock_(clock) {}
DisplayDeciderImpl(const DisplayDeciderImpl&) = delete;
DisplayDeciderImpl& operator=(const DisplayDeciderImpl&) = delete;
~DisplayDeciderImpl() override = default;
private:
// DisplayDecider implementation.
void FindNotificationsToShow(Notifications notifications,
ClientStates client_states,
Results* results) override {
Notifications throttled_notifications;
for (const auto& pair : notifications) {
auto type = pair.first;
for (auto* notification : pair.second) {
// Move unthrottled notifications to results directly.
if (notification->schedule_params.priority ==
ScheduleParams::Priority::kNoThrottle) {
results->emplace(notification->guid);
} else {
throttled_notifications[type].emplace_back(std::move(notification));
}
}
}
// Handle throttled notifications.
auto helper = std::make_unique<DecisionHelper>(
config_, clients_, clock_, std::move(throttled_notifications),
std::move(client_states));
helper->DecideNotificationToShow(results);
}
const SchedulerConfig* config_;
const std::vector<SchedulerClientType> clients_;
base::Clock* clock_;
};
} // namespace
// static
std::unique_ptr<DisplayDecider> DisplayDecider::Create(
const SchedulerConfig* config,
std::vector<SchedulerClientType> clients,
base::Clock* clock) {
return std::make_unique<DisplayDeciderImpl>(config, std::move(clients),
clock);
}
} // namespace notifications