blob: 949404baed7b5a929b8680b02145dc34ecc432be [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 "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_service.h"
#include "chrome/browser/ui/safety_hub/unused_site_permissions_service.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)) {
result_ = GetResultFromDict(
*dict.FindDict(safety_hub::kSafetyHubMenuNotificationResultKey), type);
}
}
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 (result_ != nullptr) {
result.Set(safety_hub::kSafetyHubMenuNotificationResultKey,
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 (!result_ || !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<SafetyHubService::Result> result) {
if (!is_currently_active_ && result_ &&
result_->WarrantsNewMenuNotification(*result.get())) {
should_be_shown_after_interval_ = true;
}
result_ = std::move(result);
}
std::u16string SafetyHubMenuNotification::GetNotificationString() const {
CHECK(result_);
return result_->GetNotificationString();
}
int SafetyHubMenuNotification::GetNotificationCommandId() const {
CHECK(result_);
return result_->GetNotificationCommandId();
}
SafetyHubService::Result* SafetyHubMenuNotification::GetResultForTesting()
const {
return result_.get();
}
// static
std::unique_ptr<SafetyHubService::Result>
SafetyHubMenuNotification::GetResultFromDict(
const base::Value::Dict& dict,
safety_hub::SafetyHubModuleType type) {
switch (type) {
case safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS:
return std::make_unique<
UnusedSitePermissionsService::UnusedSitePermissionsResult>(dict);
case safety_hub::SafetyHubModuleType::NOTIFICATION_PERMISSIONS:
return std::make_unique<
NotificationPermissionsReviewService::NotificationPermissionsResult>(
dict);
case safety_hub::SafetyHubModuleType::SAFE_BROWSING:
return std::make_unique<SafetyHubSafeBrowsingResult>(dict);
case safety_hub::SafetyHubModuleType::EXTENSIONS:
return std::make_unique<SafetyHubExtensionsResult>(dict);
case safety_hub::SafetyHubModuleType::PASSWORDS:
return std::make_unique<PasswordStatusCheckResult>(dict);
// No result class for version.
case safety_hub::SafetyHubModuleType::VERSION:
NOTREACHED();
return nullptr;
}
}
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_;
}