| // 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/updates/internal/update_notification_service_impl.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/time/default_clock.h" |
| #include "chrome/browser/notifications/scheduler/public/client_overview.h" |
| #include "chrome/browser/notifications/scheduler/public/notification_params.h" |
| #include "chrome/browser/notifications/scheduler/public/notification_schedule_service.h" |
| #include "chrome/browser/notifications/scheduler/public/schedule_service_utils.h" |
| #include "chrome/browser/notifications/scheduler/public/throttle_config.h" |
| #include "chrome/browser/updates/update_notification_config.h" |
| #include "chrome/browser/updates/update_notification_info.h" |
| #include "chrome/browser/updates/update_notification_service_bridge.h" |
| |
| namespace updates { |
| |
| // Maximum number of update notification should be cached in scheduler. |
| constexpr int kNumMaxNotificationsLimit = 1; |
| |
| // Maxmium number of consecutive dismiss actions from user that should be |
| // considered as negative feedback. |
| constexpr int kNumConsecutiveDismissCountCap = 2; |
| |
| // String to represents the key of the map to store update state extra. |
| const char kUpdateStateEnumKey[] = "extra_data_map_key_update_state_enum"; |
| |
| namespace { |
| |
| void BuildNotificationData(const updates::UpdateNotificationInfo& data, |
| notifications::NotificationData* out) { |
| DCHECK(out); |
| out->title = data.title; |
| out->message = data.message; |
| out->custom_data[kUpdateStateEnumKey] = base::NumberToString(data.state); |
| } |
| |
| notifications::ScheduleParams BuildScheduleParams( |
| bool should_show_immediately, |
| base::Clock* clock, |
| const UpdateNotificationConfig* config) { |
| notifications::ScheduleParams schedule_params; |
| schedule_params.impression_mapping.emplace( |
| notifications::UserFeedback::kDismiss, |
| notifications::ImpressionResult::kNegative); |
| schedule_params.impression_mapping.emplace( |
| notifications::UserFeedback::kNotHelpful, |
| notifications::ImpressionResult::kNegative); |
| if (should_show_immediately) { |
| schedule_params.deliver_time_start = base::make_optional(clock->Now()); |
| schedule_params.deliver_time_end = |
| base::make_optional(clock->Now() + base::TimeDelta::FromMinutes(1)); |
| } else { |
| notifications::TimePair actual_window; |
| notifications::NextTimeWindow(clock, config->deliver_window_morning, |
| config->deliver_window_evening, |
| &actual_window); |
| schedule_params.deliver_time_start = |
| base::make_optional(std::move(actual_window.first)); |
| schedule_params.deliver_time_end = |
| base::make_optional(std::move(actual_window.second)); |
| } |
| return schedule_params; |
| } |
| |
| base::TimeDelta GetCurrentInterval(int num_suppresion, |
| base::TimeDelta init_interval, |
| base::TimeDelta max_interval) { |
| return std::min(max_interval, (num_suppresion + 1) * init_interval); |
| } |
| |
| bool TooManyNotificationCached( |
| const notifications::ClientOverview& client_overview) { |
| return client_overview.num_scheduled_notifications >= |
| kNumMaxNotificationsLimit; |
| } |
| |
| } // namespace |
| |
| UpdateNotificationServiceImpl::UpdateNotificationServiceImpl( |
| notifications::NotificationScheduleService* schedule_service, |
| std::unique_ptr<UpdateNotificationConfig> config, |
| std::unique_ptr<UpdateNotificationServiceBridge> bridge, |
| base::Clock* clock) |
| : schedule_service_(schedule_service), |
| config_(std::move(config)), |
| bridge_(std::move(bridge)), |
| clock_(clock) {} |
| |
| UpdateNotificationServiceImpl::~UpdateNotificationServiceImpl() = default; |
| |
| void UpdateNotificationServiceImpl::Schedule(UpdateNotificationInfo data) { |
| schedule_service_->GetClientOverview( |
| notifications::SchedulerClientType::kChromeUpdate, |
| base::BindOnce(&UpdateNotificationServiceImpl::ScheduleInternal, |
| weak_ptr_factory_.GetWeakPtr(), std::move(data))); |
| } |
| |
| void UpdateNotificationServiceImpl::ScheduleInternal( |
| UpdateNotificationInfo data, |
| notifications::ClientOverview client_overview) { |
| if (TooManyNotificationCached(client_overview)) |
| return; |
| |
| notifications::NotificationData notification_data; |
| BuildNotificationData(data, ¬ification_data); |
| auto params = std::make_unique<notifications::NotificationParams>( |
| notifications::SchedulerClientType::kChromeUpdate, |
| std::move(notification_data), |
| BuildScheduleParams(data.should_show_immediately, clock_, config_.get())); |
| params->enable_ihnr_buttons = true; |
| schedule_service_->Schedule(std::move(params)); |
| } |
| |
| void UpdateNotificationServiceImpl::OnUserClick(const ExtraData& extra) { |
| DCHECK(base::Contains(extra, kUpdateStateEnumKey)); |
| int state = 0; |
| auto res = base::StringToInt(extra.at(kUpdateStateEnumKey), &state); |
| DCHECK(res); |
| bridge_->LaunchChromeActivity(state); |
| } |
| |
| void UpdateNotificationServiceImpl::GetThrottleConfig( |
| ThrottleConfigCallback callback) { |
| schedule_service_->GetClientOverview( |
| notifications::SchedulerClientType::kChromeUpdate, |
| base::BindOnce(&UpdateNotificationServiceImpl::DetermineThrottleConfig, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void UpdateNotificationServiceImpl::DetermineThrottleConfig( |
| ThrottleConfigCallback callback, |
| notifications::ClientOverview client_overview) { |
| auto throttle_config = std::make_unique<notifications::ThrottleConfig>(); |
| // 2 consecutive dismiss will cause a suppression. |
| throttle_config->negative_action_count_threshold = |
| kNumConsecutiveDismissCountCap; |
| auto num_suppresion = client_overview.impression_detail.num_negative_events; |
| // Next suppression duration is proportional to the number of suppression |
| // events, ceiled with a maximum duration interval. |
| throttle_config->suppression_duration = GetCurrentInterval( |
| num_suppresion, config_->init_interval, config_->max_interval); |
| std::move(callback).Run(std::move(throttle_config)); |
| } |
| |
| void UpdateNotificationServiceImpl::BeforeShowNotification( |
| std::unique_ptr<notifications::NotificationData> notification_data, |
| NotificationDataCallback callback) { |
| schedule_service_->GetClientOverview( |
| notifications::SchedulerClientType::kChromeUpdate, |
| base::BindOnce(&UpdateNotificationServiceImpl::MaybeShowNotification, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(notification_data), std::move(callback))); |
| } |
| |
| bool UpdateNotificationServiceImpl::TooSoonForNextNotification( |
| const notifications::ClientOverview& client_overview) { |
| auto last_shown_timestamp = client_overview.impression_detail.last_shown_ts; |
| auto next_notificaiton_interval = |
| GetCurrentInterval(client_overview.impression_detail.num_negative_events, |
| config_->init_interval, config_->max_interval); |
| return last_shown_timestamp.has_value() && |
| next_notificaiton_interval >= |
| clock_->Now() - last_shown_timestamp.value(); |
| } |
| |
| void UpdateNotificationServiceImpl::MaybeShowNotification( |
| std::unique_ptr<notifications::NotificationData> notification_data, |
| NotificationDataCallback callback, |
| notifications::ClientOverview client_overview) { |
| bool should_show_notification = |
| config_->is_enabled && !TooSoonForNextNotification(client_overview) && |
| !TooManyNotificationCached(client_overview); |
| |
| std::move(callback).Run( |
| should_show_notification ? std::move(notification_data) : nullptr); |
| } |
| |
| } // namespace updates |