Commit 8603e84c authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Notification scheduler: Implements a class to schedule background task.

This CL adds the BackgroundTaskCoordinator class, which determines the
time that background task should run.

TBR=peter@chromium.org

Bug: 963283,930968
Change-Id: I82ecd3bb15ac2df73a546afa94c25fad5fded36d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1614501
Commit-Queue: Xing Liu <xingliu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662477}
parent f4547fea
...@@ -18,3 +18,7 @@ void NotificationBackgroundTaskSchedulerImpl::Schedule( ...@@ -18,3 +18,7 @@ void NotificationBackgroundTaskSchedulerImpl::Schedule(
// TODO(xingliu): Implements this for non-Android platform. // TODO(xingliu): Implements this for non-Android platform.
NOTIMPLEMENTED(); NOTIMPLEMENTED();
} }
void NotificationBackgroundTaskSchedulerImpl::Cancel() {
NOTIMPLEMENTED();
}
...@@ -20,6 +20,7 @@ class NotificationBackgroundTaskSchedulerImpl ...@@ -20,6 +20,7 @@ class NotificationBackgroundTaskSchedulerImpl
// NotificationBackgroundTaskScheduler implementation. // NotificationBackgroundTaskScheduler implementation.
void Schedule(base::TimeDelta window_start, void Schedule(base::TimeDelta window_start,
base::TimeDelta window_end) override; base::TimeDelta window_end) override;
void Cancel() override;
DISALLOW_COPY_AND_ASSIGN(NotificationBackgroundTaskSchedulerImpl); DISALLOW_COPY_AND_ASSIGN(NotificationBackgroundTaskSchedulerImpl);
}; };
......
...@@ -66,6 +66,8 @@ source_set("lib") { ...@@ -66,6 +66,8 @@ source_set("lib") {
] ]
sources = [ sources = [
"background_task_coordinator.cc",
"background_task_coordinator.h",
"collection_store.h", "collection_store.h",
"display_decider.cc", "display_decider.cc",
"display_decider.h", "display_decider.h",
...@@ -117,6 +119,7 @@ source_set("lib") { ...@@ -117,6 +119,7 @@ source_set("lib") {
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"background_task_coordinator_unittest.cc",
"display_decider_unittest.cc", "display_decider_unittest.cc",
"distribution_policy_unittest.cc", "distribution_policy_unittest.cc",
"icon_store_unittest.cc", "icon_store_unittest.cc",
......
// 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/background_task_coordinator.h"
#include <algorithm>
#include <utility>
#include "base/numerics/ranges.h"
#include "base/optional.h"
#include "base/time/clock.h"
#include "chrome/browser/notifications/scheduler/impression_types.h"
#include "chrome/browser/notifications/scheduler/notification_background_task_scheduler.h"
#include "chrome/browser/notifications/scheduler/scheduler_config.h"
#include "chrome/browser/notifications/scheduler/scheduler_utils.h"
namespace notifications {
namespace {
class BackgroundTaskCoordinatorHelper {
public:
BackgroundTaskCoordinatorHelper(
NotificationBackgroundTaskScheduler* background_task,
const SchedulerConfig* config,
base::Clock* clock)
: background_task_(background_task), config_(config), clock_(clock) {}
~BackgroundTaskCoordinatorHelper() = default;
void ScheduleBackgroundTask(
BackgroundTaskCoordinator::Notifications notifications,
BackgroundTaskCoordinator::ClientStates client_states,
SchedulerTaskTime task_start_time) {
if (notifications.empty()) {
background_task_->Cancel();
return;
}
std::map<SchedulerClientType, int> shown_per_type;
int shown_total = 0;
SchedulerClientType last_shown_type = SchedulerClientType::kUnknown;
NotificationsShownToday(client_states, &shown_per_type, &shown_total,
&last_shown_type);
bool reach_max_today_all_type =
shown_total >= config_->max_daily_shown_all_type;
base::Time next_morning = NextMorning();
base::Time this_evening = ThisEvening();
for (const auto& pair : notifications) {
auto type = pair.first;
auto it = client_states.find(type);
if (pair.second.empty() || (it == client_states.end()))
continue;
const ClientState* client_state = it->second;
// Try to schedule on the day that suppression expires.
if (client_state->suppression_info.has_value()) {
const auto& suppression = client_state->suppression_info.value();
base::Time suppression_expire;
ToLocalHour(config_->morning_task_hour, suppression.ReleaseTime(),
0 /*day_delta*/, &suppression_expire);
MaybeUpdateBackgroundTaskTime(
std::max(suppression_expire, next_morning));
continue;
}
// Has met the quota for this notification type or for all types, only can
// send more on next day.
bool reach_max_today =
shown_per_type[type] >= client_state->current_max_daily_show;
if (reach_max_today || reach_max_today_all_type) {
MaybeUpdateBackgroundTaskTime(next_morning);
continue;
}
switch (task_start_time) {
case SchedulerTaskTime::kMorning:
// Still can send more in the evening.
MaybeUpdateBackgroundTaskTime(this_evening);
break;
case SchedulerTaskTime::kEvening:
// Wait until the next calendar day.
MaybeUpdateBackgroundTaskTime(next_morning);
break;
case SchedulerTaskTime::kUnknown:
// TODO(xingliu): Support arbitrary time background task.
NOTIMPLEMENTED();
break;
}
}
ScheduleBackgroundTaskInternal();
}
private:
// Returns the morning background task time on the next day.
base::Time NextMorning() {
base::Time next_morning;
bool success = ToLocalHour(config_->morning_task_hour, clock_->Now(),
1 /*day_delta*/, &next_morning);
DCHECK(success);
return next_morning;
}
// Returns the evening background task time on today.
base::Time ThisEvening() {
base::Time this_evening;
bool success = ToLocalHour(config_->evening_task_hour, clock_->Now(),
0 /*day_delta*/, &this_evening);
DCHECK(success);
return this_evening;
}
void MaybeUpdateBackgroundTaskTime(const base::Time& time) {
if (!background_task_time_.has_value() ||
time < background_task_time_.value())
background_task_time_ = time;
}
void ScheduleBackgroundTaskInternal() {
if (!background_task_time_.has_value())
return;
base::TimeDelta window_start_time =
background_task_time_.value() - clock_->Now();
window_start_time = base::ClampToRange(window_start_time, base::TimeDelta(),
base::TimeDelta::Max());
// TODO(xingliu): Pass the SchedulerTaskTime tag to background task to
// support arbitrary time background task.
background_task_->Schedule(
window_start_time,
window_start_time + config_->background_task_window_duration);
}
NotificationBackgroundTaskScheduler* background_task_;
const SchedulerConfig* config_;
base::Clock* clock_;
base::Optional<base::Time> background_task_time_;
DISALLOW_COPY_AND_ASSIGN(BackgroundTaskCoordinatorHelper);
};
} // namespace
BackgroundTaskCoordinator::BackgroundTaskCoordinator(
std::unique_ptr<NotificationBackgroundTaskScheduler> background_task,
const SchedulerConfig* config,
base::Clock* clock)
: background_task_(std::move(background_task)),
config_(config),
clock_(clock) {}
BackgroundTaskCoordinator::~BackgroundTaskCoordinator() = default;
void BackgroundTaskCoordinator::ScheduleBackgroundTask(
Notifications notifications,
ClientStates client_states,
SchedulerTaskTime task_start_time) {
auto helper = std::make_unique<BackgroundTaskCoordinatorHelper>(
background_task_.get(), config_, clock_);
helper->ScheduleBackgroundTask(std::move(notifications),
std::move(client_states), task_start_time);
}
} // namespace notifications
// 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.
#ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_BACKGROUND_TASK_COORDINATOR_H_
#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_BACKGROUND_TASK_COORDINATOR_H_
#include <map>
#include <memory>
#include <vector>
#include "base/macros.h"
#include "chrome/browser/notifications/scheduler/internal_types.h"
#include "chrome/browser/notifications/scheduler/notification_scheduler_types.h"
namespace base {
class Clock;
} // namespace base
namespace notifications {
class NotificationBackgroundTaskScheduler;
struct ClientState;
struct NotificationEntry;
struct SchedulerConfig;
// Schedules background task at the right time based on scheduled notification
// data and impression data.
class BackgroundTaskCoordinator {
public:
using Notifications =
std::map<SchedulerClientType, std::vector<const NotificationEntry*>>;
using ClientStates = std::map<SchedulerClientType, const ClientState*>;
BackgroundTaskCoordinator(
std::unique_ptr<NotificationBackgroundTaskScheduler> background_task,
const SchedulerConfig* config,
base::Clock* clock);
virtual ~BackgroundTaskCoordinator();
// Schedule background task based on current notification in the storage.
virtual void ScheduleBackgroundTask(Notifications notifications,
ClientStates client_states,
SchedulerTaskTime task_start_time);
private:
// The class that actually schedules platform background task.
std::unique_ptr<NotificationBackgroundTaskScheduler> background_task_;
// System configuration.
const SchedulerConfig* config_;
// Clock to query the current timestamp.
base::Clock* clock_;
DISALLOW_COPY_AND_ASSIGN(BackgroundTaskCoordinator);
};
} // namespace notifications
#endif // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_BACKGROUND_TASK_COORDINATOR_H_
...@@ -64,31 +64,15 @@ class DecisionHelper { ...@@ -64,31 +64,15 @@ class DecisionHelper {
} }
void CountNotificationsShownToday() { void CountNotificationsShownToday() {
base::Time last_shown_time; for (const auto& pair : client_states_) {
base::Time now(base::Time::Now()); auto type = pair.first;
base::Time beginning_of_today;
bool success = ToLocalHour(0, now, 0, &beginning_of_today);
DCHECK(success);
for (const auto& state : client_states_) {
const auto* client_state = state.second;
// TODO(xingliu): Ensure deprecated clients will not have data in storage. // TODO(xingliu): Ensure deprecated clients will not have data in storage.
DCHECK(std::find(clients_.begin(), clients_.end(), client_state->type) != DCHECK(std::find(clients_.begin(), clients_.end(), type) !=
clients_.end()); clients_.end());
for (const auto& impression : client_state->impressions) {
// Tracks last notification shown to the user.
if (impression.create_time > last_shown_time) {
last_shown_time = impression.create_time;
last_shown_type_ = client_state->type;
}
// Count notification shown today.
if (impression.create_time >= beginning_of_today) {
shown_per_type_[client_state->type]++;
++shown_;
}
}
} }
NotificationsShownToday(client_states_, &shown_per_type_, &shown_,
&last_shown_type_);
} }
// Picks a list of notifications to show. // Picks a list of notifications to show.
......
...@@ -28,6 +28,10 @@ bool SuppressionInfo::operator==(const SuppressionInfo& other) const { ...@@ -28,6 +28,10 @@ bool SuppressionInfo::operator==(const SuppressionInfo& other) const {
duration == other.duration && recover_goal == other.recover_goal; duration == other.duration && recover_goal == other.recover_goal;
} }
base::Time SuppressionInfo::ReleaseTime() const {
return last_trigger_time + duration;
}
ClientState::ClientState() ClientState::ClientState()
: type(SchedulerClientType::kUnknown), current_max_daily_show(0) {} : type(SchedulerClientType::kUnknown), current_max_daily_show(0) {}
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <deque> #include <deque>
#include <map> #include <map>
#include <string>
#include "base/optional.h" #include "base/optional.h"
#include "base/time/time.h" #include "base/time/time.h"
...@@ -58,6 +59,9 @@ struct SuppressionInfo { ...@@ -58,6 +59,9 @@ struct SuppressionInfo {
~SuppressionInfo() = default; ~SuppressionInfo() = default;
bool operator==(const SuppressionInfo& other) const; bool operator==(const SuppressionInfo& other) const;
// Time that the suppression should release.
base::Time ReleaseTime() const;
// The last supression trigger time. // The last supression trigger time.
base::Time last_trigger_time; base::Time last_trigger_time;
......
...@@ -35,10 +35,14 @@ class NotificationBackgroundTaskScheduler { ...@@ -35,10 +35,14 @@ class NotificationBackgroundTaskScheduler {
}; };
// Schedules a background task in a time window between |window_start| and // Schedules a background task in a time window between |window_start| and
// |window_end|. // |window_end|. This will update the current background task. Only one
// background task exists for notification scheduler.
virtual void Schedule(base::TimeDelta window_start, virtual void Schedule(base::TimeDelta window_start,
base::TimeDelta window_end) = 0; base::TimeDelta window_end) = 0;
// Cancels the background task.
virtual void Cancel() = 0;
virtual ~NotificationBackgroundTaskScheduler() = default; virtual ~NotificationBackgroundTaskScheduler() = default;
protected: protected:
......
...@@ -164,6 +164,9 @@ class NotificationSchedulerImpl ...@@ -164,6 +164,9 @@ class NotificationSchedulerImpl
context_->config(), context_->clients(), DistributionPolicy::Create(), context_->config(), context_->clients(), DistributionPolicy::Create(),
task_start_time, std::move(notifications), std::move(client_state_ptrs), task_start_time, std::move(notifications), std::move(client_state_ptrs),
&results); &results);
// TODO(xingliu): Update impression data after notification shown.
// See https://crbug.com/965133.
for (const auto& guid : results) { for (const auto& guid : results) {
context_->notification_manager()->DisplayNotification(guid); context_->notification_manager()->DisplayNotification(guid);
} }
......
...@@ -24,6 +24,10 @@ constexpr int kDefaultEveningTaskHour = 18; ...@@ -24,6 +24,10 @@ constexpr int kDefaultEveningTaskHour = 18;
// dismiss event. // dismiss event.
constexpr base::TimeDelta kDefaultDimissDuration = base::TimeDelta::FromDays(7); constexpr base::TimeDelta kDefaultDimissDuration = base::TimeDelta::FromDays(7);
// Default background task time window duration.
constexpr base::TimeDelta kDefaultBackgroundTaskWindowDuration =
base::TimeDelta::FromHours(1);
// static // static
std::unique_ptr<SchedulerConfig> SchedulerConfig::Create() { std::unique_ptr<SchedulerConfig> SchedulerConfig::Create() {
return std::make_unique<SchedulerConfig>(); return std::make_unique<SchedulerConfig>();
...@@ -37,7 +41,8 @@ SchedulerConfig::SchedulerConfig() ...@@ -37,7 +41,8 @@ SchedulerConfig::SchedulerConfig()
dismiss_count(3), dismiss_count(3),
dismiss_duration(kDefaultDimissDuration), dismiss_duration(kDefaultDimissDuration),
morning_task_hour(kDefaultMorningTaskHour), morning_task_hour(kDefaultMorningTaskHour),
evening_task_hour(kDefaultEveningTaskHour) { evening_task_hour(kDefaultEveningTaskHour),
background_task_window_duration(kDefaultBackgroundTaskWindowDuration) {
// TODO(xingliu): Add constructor using finch data. // TODO(xingliu): Add constructor using finch data.
} }
......
...@@ -49,6 +49,9 @@ struct SchedulerConfig { ...@@ -49,6 +49,9 @@ struct SchedulerConfig {
// scheduler. // scheduler.
int evening_task_hour; int evening_task_hour;
// The time window to launch the background task.
base::TimeDelta background_task_window_duration;
private: private:
DISALLOW_COPY_AND_ASSIGN(SchedulerConfig); DISALLOW_COPY_AND_ASSIGN(SchedulerConfig);
}; };
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "chrome/browser/notifications/scheduler/scheduler_utils.h" #include "chrome/browser/notifications/scheduler/scheduler_utils.h"
#include "chrome/browser/notifications/scheduler/impression_types.h"
namespace notifications { namespace notifications {
bool ToLocalHour(int hour, bool ToLocalHour(int hour,
...@@ -27,4 +29,33 @@ bool ToLocalHour(int hour, ...@@ -27,4 +29,33 @@ bool ToLocalHour(int hour,
return base::Time::FromLocalExploded(another_day_exploded, out); return base::Time::FromLocalExploded(another_day_exploded, out);
} }
void NotificationsShownToday(
const std::map<SchedulerClientType, const ClientState*>& client_states,
std::map<SchedulerClientType, int>* shown_per_type,
int* shown_total,
SchedulerClientType* last_shown_type) {
base::Time last_shown_time;
base::Time now(base::Time::Now());
base::Time beginning_of_today;
bool success = ToLocalHour(0, now, 0, &beginning_of_today);
DCHECK(success);
for (const auto& state : client_states) {
const auto* client_state = state.second;
for (const auto& impression : client_state->impressions) {
// Tracks last notification shown to the user.
if (impression.create_time > last_shown_time) {
last_shown_time = impression.create_time;
*last_shown_type = client_state->type;
}
// Count notification shown today.
if (impression.create_time >= beginning_of_today) {
(*shown_per_type)[client_state->type]++;
++(*shown_total);
}
}
}
}
} // namespace notifications } // namespace notifications
...@@ -5,10 +5,15 @@ ...@@ -5,10 +5,15 @@
#ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULER_UTILS_H_ #ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULER_UTILS_H_
#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULER_UTILS_H_ #define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULER_UTILS_H_
#include <map>
#include "base/time/time.h" #include "base/time/time.h"
#include "chrome/browser/notifications/scheduler/notification_scheduler_types.h"
namespace notifications { namespace notifications {
struct ClientState;
// Retrieves the time stamp of a certain hour at a certain day from today. // Retrieves the time stamp of a certain hour at a certain day from today.
// |hour| must be in the range of [0, 23]. // |hour| must be in the range of [0, 23].
// |today| is a timestamp to define today, usually caller can directly pass in // |today| is a timestamp to define today, usually caller can directly pass in
...@@ -20,6 +25,13 @@ bool ToLocalHour(int hour, ...@@ -20,6 +25,13 @@ bool ToLocalHour(int hour,
int day_delta, int day_delta,
base::Time* out); base::Time* out);
// Calculates the notifications shown today from impression data.
void NotificationsShownToday(
const std::map<SchedulerClientType, const ClientState*>& client_states,
std::map<SchedulerClientType, int>* shown_per_type,
int* shown_total,
SchedulerClientType* last_shown_type);
} // namespace notifications } // namespace notifications
#endif // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULER_UTILS_H_ #endif // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_SCHEDULER_UTILS_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment