blob: e3c43e5d507d45b5500667d2116b0d7bef52bd7f [file] [log] [blame]
// Copyright 2023 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/ui/safety_hub/menu_notification.h"
#include <memory>
#include <string>
#include "base/json/values_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ui/safety_hub/extensions_result.h"
#include "chrome/browser/ui/safety_hub/notification_permission_review_service.h"
#include "chrome/browser/ui/safety_hub/password_status_check_result.h"
#include "chrome/browser/ui/safety_hub/safe_browsing_result.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_result.h"
SafetyHubMenuNotification::SafetyHubMenuNotification(
safety_hub::SafetyHubModuleType type)
: first_impression_time_(std::nullopt),
last_impression_time_(std::nullopt),
show_only_after_(std::nullopt),
module_type_(type) {}
SafetyHubMenuNotification::~SafetyHubMenuNotification() = default;
SafetyHubMenuNotification::SafetyHubMenuNotification(
const base::Value::Dict& dict,
safety_hub::SafetyHubModuleType type)
: module_type_(type) {
is_currently_active_ =
dict.FindBool(safety_hub::kSafetyHubMenuNotificationActiveKey).value();
impression_count_ =
dict.FindInt(safety_hub::kSafetyHubMenuNotificationImpressionCountKey)
.value_or(0);
all_time_notification_count_ =
dict.FindInt(safety_hub::kSafetyHubMenuNotificationAllTimeCountKey)
.value_or(0);
first_impression_time_ = base::ValueToTime(
dict.Find(safety_hub::kSafetyHubMenuNotificationFirstImpressionKey));
last_impression_time_ = base::ValueToTime(
dict.Find(safety_hub::kSafetyHubMenuNotificationLastImpressionKey));
show_only_after_ = base::ValueToTime(
dict.Find(safety_hub::kSafetyHubMenuNotificationShowAfterTimeKey));
if (dict.contains(safety_hub::kSafetyHubMenuNotificationResultKey)) {
prev_stored_result_ =
dict.FindDict(safety_hub::kSafetyHubMenuNotificationResultKey)->Clone();
}
}
base::Value::Dict SafetyHubMenuNotification::ToDictValue() const {
base::Value::Dict result;
result.Set(safety_hub::kSafetyHubMenuNotificationActiveKey,
is_currently_active_);
if (impression_count_ != 0) {
result.Set(safety_hub::kSafetyHubMenuNotificationImpressionCountKey,
impression_count_);
}
if (first_impression_time_.has_value()) {
result.Set(safety_hub::kSafetyHubMenuNotificationFirstImpressionKey,
base::TimeToValue(first_impression_time_.value()));
}
if (last_impression_time_.has_value()) {
result.Set(safety_hub::kSafetyHubMenuNotificationLastImpressionKey,
base::TimeToValue(last_impression_time_.value()));
}
if (all_time_notification_count_ != 0) {
result.Set(safety_hub::kSafetyHubMenuNotificationAllTimeCountKey,
all_time_notification_count_);
}
if (show_only_after_.has_value()) {
result.Set(safety_hub::kSafetyHubMenuNotificationShowAfterTimeKey,
base::TimeToValue(show_only_after_.value()));
}
if (current_result_ != nullptr) {
result.Set(safety_hub::kSafetyHubMenuNotificationResultKey,
current_result_->ToDictValue());
}
return result;
}
void SafetyHubMenuNotification::Show() {
++impression_count_;
if (!first_impression_time_.has_value()) {
is_currently_active_ = true;
should_be_shown_after_interval_ = false;
first_impression_time_ = base::Time::Now();
}
last_impression_time_ = base::Time::Now();
}
void SafetyHubMenuNotification::Dismiss() {
is_currently_active_ = false;
impression_count_ = 0;
first_impression_time_ = std::nullopt;
++all_time_notification_count_;
}
// The maximum all time impressions for a notification are passed on each call
// instead of determined in the constructor to make these easier
// Finch-configurable as the constructor will only called once initially for
// each Safety Hub module, after which it will always be read from disk.
bool SafetyHubMenuNotification::ShouldBeShown(
base::TimeDelta interval,
int max_all_time_impressions) const {
// The type of notification has already been shown the maximum number of times
// in the total lifetime.
if (max_all_time_impressions > 0 &&
all_time_notification_count_ >= max_all_time_impressions) {
return false;
}
// There is no associated result, or the result does not meet the bar for menu
// notifications.
if (!current_result_ || !current_result_->IsTriggerForMenuNotification()) {
return false;
}
// Do not show notification if it is too soon.
if (show_only_after_.has_value() &&
base::Time::Now() < show_only_after_.value()) {
return false;
}
// Notifications that have never been shown can be shown as long as the result
// is a trigger.
if (!HasAnyNotificationBeenShown()) {
return true;
}
// For active notifications, the notification should be shown if it is either
// not shown enough times, or not sufficiently long enough.
if (is_currently_active_) {
return (!IsShownEnough());
}
// For notifications that are inactive, showing the notification is determined
// by whether the interval has passed.
return should_be_shown_after_interval_ && HasIntervalPassed(interval);
}
bool SafetyHubMenuNotification::IsCurrentlyActive() const {
return is_currently_active_;
}
bool SafetyHubMenuNotification::IsShownEnough() const {
// The notification has never been shown before.
if (!first_impression_time_.has_value() ||
!last_impression_time_.has_value()) {
return false;
}
bool isShownEnoughDays =
(base::Time::Now() - first_impression_time_.value()) >=
kSafetyHubMenuNotificationMinNotificationDuration;
bool isShownEnoughTimes =
impression_count_ >= kSafetyHubMenuNotificationMinImpressionCount;
return isShownEnoughDays && isShownEnoughTimes;
}
bool SafetyHubMenuNotification::HasIntervalPassed(
base::TimeDelta interval) const {
// Notification has never been shown, so no interval in this case.
if (!HasAnyNotificationBeenShown()) {
return true;
}
return base::Time::Now() - last_impression_time_.value() >= interval;
}
bool SafetyHubMenuNotification::HasAnyNotificationBeenShown() const {
return last_impression_time_.has_value();
}
void SafetyHubMenuNotification::UpdateResult(
std::unique_ptr<SafetyHubResult> new_result) {
// Use the latest available result. This is either the current result when a
// new result was received, or, if it is unavailble, the result that was
// stored on the disk.
base::Value::Dict previous_result_dict = current_result_
? current_result_->ToDictValue()
: prev_stored_result_.Clone();
// For notifications that are not currently active yet, and have a previous
// result, we have to determine whether the notification should be shown after
// the interval by comparing the old (stored) data with the new data to check
// whether it warrants a new notification.
if (!is_currently_active_ && !previous_result_dict.empty() &&
new_result->WarrantsNewMenuNotification(previous_result_dict)) {
should_be_shown_after_interval_ = true;
}
current_result_ = std::move(new_result);
}
std::u16string SafetyHubMenuNotification::GetNotificationString() const {
CHECK(current_result_);
return current_result_->GetNotificationString();
}
int SafetyHubMenuNotification::GetNotificationCommandId() const {
CHECK(current_result_);
return current_result_->GetNotificationCommandId();
}
SafetyHubResult* SafetyHubMenuNotification::GetResultForTesting() const {
return current_result_.get();
}
void SafetyHubMenuNotification::SetOnlyShowAfter(base::Time time) {
show_only_after_ = time;
}
void SafetyHubMenuNotification::ResetAllTimeNotificationCount() {
all_time_notification_count_ = 0;
}
safety_hub::SafetyHubModuleType SafetyHubMenuNotification::GetModuleType()
const {
return module_type_;
}