blob: 34e276c3aae20d46b09456c8e7aff8803a83aa57 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/feature_engagement/internal/feature_config_condition_validator.h"
#include <algorithm>
#include <optional>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/notreached.h"
#include "components/feature_engagement/internal/availability_model.h"
#include "components/feature_engagement/internal/display_lock_controller.h"
#include "components/feature_engagement/internal/event_model_reader.h"
#include "components/feature_engagement/internal/proto/feature_event.pb.h"
#include "components/feature_engagement/internal/time_provider.h"
#include "components/feature_engagement/public/configuration.h"
#include "components/feature_engagement/public/feature_list.h"
namespace feature_engagement {
FeatureConfigConditionValidator::FeatureConfigConditionValidator() = default;
FeatureConfigConditionValidator::~FeatureConfigConditionValidator() = default;
ConditionValidator::Result FeatureConfigConditionValidator::MeetsConditions(
const base::Feature& feature,
const FeatureConfig& config,
const std::vector<GroupConfig>& group_configs,
const EventModelReader& event_model_reader,
const AvailabilityModel& availability_model,
const DisplayLockController& display_lock_controller,
const Configuration* configuration,
const TimeProvider& time_provider) const {
uint32_t current_day = time_provider.GetCurrentDay();
ConditionValidator::Result result(true);
result.event_model_ready_ok = event_model_reader.IsReady();
result.currently_showing_ok = !IsBlocked(feature, config, configuration);
result.feature_enabled_ok = base::FeatureList::IsEnabled(feature);
result.config_ok = config.valid;
result.used_ok =
EventConfigMeetsConditions(config.used, event_model_reader, current_day);
result.trigger_ok = EventConfigMeetsConditions(
config.trigger, event_model_reader, current_day);
for (const auto& event_config : config.event_configs) {
result.preconditions_ok &= EventConfigMeetsConditions(
event_config, event_model_reader, current_day);
}
result.session_rate_ok =
SessionRateMeetsConditions(config.session_rate, feature);
result.availability_model_ready_ok = availability_model.IsReady();
result.availability_ok = AvailabilityMeetsConditions(
feature, config.availability, availability_model, current_day);
result.display_lock_ok = !display_lock_controller.IsDisplayLocked();
result.snooze_expiration_ok =
!event_model_reader.IsSnoozeDismissed(config.trigger.name) &&
(event_model_reader.GetLastSnoozeTimestamp(config.trigger.name) <
time_provider.Now() - base::Days(config.snooze_params.snooze_interval));
result.priority_notification_ok =
!pending_priority_notification_.has_value() ||
pending_priority_notification_.value() == feature.name;
result.should_show_snooze = result.snooze_expiration_ok &&
event_model_reader.GetSnoozeCount(
config.trigger.name, config.trigger.window,
current_day) < config.snooze_params.max_limit;
// Add on group additions
for (const auto& group_config : group_configs) {
bool valid = group_config.valid;
result.config_ok &= valid;
result.groups_ok &= valid;
bool trigger_ok = EventConfigMeetsConditions(
group_config.trigger, event_model_reader, current_day);
result.trigger_ok &= trigger_ok;
result.groups_ok &= trigger_ok;
for (const auto& event_config : group_config.event_configs) {
bool precondition_ok = EventConfigMeetsConditions(
event_config, event_model_reader, current_day);
result.preconditions_ok &= precondition_ok;
result.groups_ok &= precondition_ok;
}
bool session_rate_ok =
SessionRateMeetsConditions(group_config.session_rate, feature);
result.session_rate_ok &= session_rate_ok;
result.groups_ok &= session_rate_ok;
}
return result;
}
void FeatureConfigConditionValidator::NotifyIsShowing(
const base::Feature& feature,
const FeatureConfig& config,
const std::vector<std::string>& all_feature_names) {
DCHECK(base::FeatureList::IsEnabled(feature));
currently_showing_features_.insert(feature.name);
switch (config.session_rate_impact.type) {
case SessionRateImpact::Type::ALL:
for (const std::string& feature_name : all_feature_names)
++times_shown_for_feature_[feature_name];
break;
case SessionRateImpact::Type::NONE:
// Intentionally ignore, since no features should be impacted.
break;
case SessionRateImpact::Type::EXPLICIT:
DCHECK(config.session_rate_impact.affected_features.has_value());
for (const std::string& feature_name :
config.session_rate_impact.affected_features.value()) {
DCHECK(base::Contains(all_feature_names, feature_name));
++times_shown_for_feature_[feature_name];
}
break;
default:
// All cases should be covered.
NOTREACHED();
}
}
void FeatureConfigConditionValidator::NotifyDismissed(
const base::Feature& feature) {
currently_showing_features_.erase(feature.name);
}
bool FeatureConfigConditionValidator::EventConfigMeetsConditions(
const EventConfig& event_config,
const EventModelReader& event_model_reader,
uint32_t current_day) const {
uint32_t event_count = event_model_reader.GetEventCount(
event_config.name, current_day, event_config.window);
return event_config.comparator.MeetsCriteria(event_count);
}
void FeatureConfigConditionValidator::SetPriorityNotification(
const std::optional<std::string>& feature) {
DCHECK(!pending_priority_notification_.has_value() || !feature.has_value());
pending_priority_notification_ = feature;
}
std::optional<std::string>
FeatureConfigConditionValidator::GetPendingPriorityNotification() {
return pending_priority_notification_;
}
void FeatureConfigConditionValidator::ResetSession() {
times_shown_for_feature_.clear();
}
bool FeatureConfigConditionValidator::AvailabilityMeetsConditions(
const base::Feature& feature,
Comparator comparator,
const AvailabilityModel& availability_model,
uint32_t current_day) const {
if (comparator.type == ANY)
return true;
std::optional<uint32_t> availability_day =
availability_model.GetAvailability(feature);
if (!availability_day.has_value())
return false;
uint32_t days_available = current_day - availability_day.value();
// Ensure that availability days never wrap around.
if (availability_day.value() > current_day)
days_available = 0u;
return comparator.MeetsCriteria(days_available);
}
bool FeatureConfigConditionValidator::SessionRateMeetsConditions(
const Comparator session_rate,
const base::Feature& feature) const {
const auto it = times_shown_for_feature_.find(feature.name);
if (it == times_shown_for_feature_.end())
return session_rate.MeetsCriteria(0u);
return session_rate.MeetsCriteria(it->second);
}
bool FeatureConfigConditionValidator::IsBlocked(
const base::Feature& feature,
const FeatureConfig& config,
const Configuration* configuration) const {
switch (config.blocked_by.type) {
case BlockedBy::Type::NONE:
return false;
case BlockedBy::Type::ALL: {
bool is_blocked = false;
for (const std::string& currently_showing_feature :
currently_showing_features_) {
auto currently_showing_feature_config =
configuration->GetFeatureConfigByName(currently_showing_feature);
if (currently_showing_feature_config.blocking.type ==
Blocking::Type::NONE)
continue;
is_blocked = true;
}
return is_blocked;
}
case BlockedBy::Type::EXPLICIT:
for (const std::string& feature_name :
*config.blocked_by.affected_features) {
if (base::Contains(currently_showing_features_, feature_name))
return true;
}
return false;
default:
// All cases should be covered.
NOTREACHED();
}
}
} // namespace feature_engagement