blob: cf7c5c0f7d9b704f82ece226c8e2e02cd2a926cd [file] [log] [blame]
// Copyright 2017 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 "components/feature_engagement/internal/feature_config_condition_validator.h"
#include <algorithm>
#include <string>
#include <vector>
#include "base/feature_list.h"
#include "base/stl_util.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.h"
#include "components/feature_engagement/internal/proto/feature_event.pb.h"
#include "components/feature_engagement/public/configuration.h"
#include "components/feature_engagement/public/feature_list.h"
namespace feature_engagement {
FeatureConfigConditionValidator::FeatureConfigConditionValidator()
: currently_showing_(false) {}
FeatureConfigConditionValidator::~FeatureConfigConditionValidator() = default;
ConditionValidator::Result FeatureConfigConditionValidator::MeetsConditions(
const base::Feature& feature,
const FeatureConfig& config,
const EventModel& event_model,
const AvailabilityModel& availability_model,
const DisplayLockController& display_lock_controller,
uint32_t current_day) const {
ConditionValidator::Result result(true);
result.event_model_ready_ok = event_model.IsReady();
result.currently_showing_ok = !currently_showing_;
result.feature_enabled_ok = base::FeatureList::IsEnabled(feature);
result.config_ok = config.valid;
result.used_ok =
EventConfigMeetsConditions(config.used, event_model, current_day);
result.trigger_ok =
EventConfigMeetsConditions(config.trigger, event_model, current_day);
for (const auto& event_config : config.event_configs) {
result.preconditions_ok &=
EventConfigMeetsConditions(event_config, event_model, 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();
return result;
}
void FeatureConfigConditionValidator::NotifyIsShowing(
const base::Feature& feature,
const FeatureConfig& config,
const std::vector<std::string>& all_feature_names) {
DCHECK(!currently_showing_);
DCHECK(base::FeatureList::IsEnabled(feature));
currently_showing_ = true;
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::ContainsValue(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_ = false;
}
bool FeatureConfigConditionValidator::EventConfigMeetsConditions(
const EventConfig& event_config,
const EventModel& event_model,
uint32_t current_day) const {
const Event* event = event_model.GetEvent(event_config.name);
// If no events are found, the requirement must be met with 0 elements.
// Also, if the window is 0 days, there will never be any events.
if (event == nullptr || event_config.window == 0u)
return event_config.comparator.MeetsCriteria(0u);
DCHECK(event_config.window >= 0);
// A window of N=0: Nothing should be counted.
// A window of N=1: |current_day| should be counted.
// A window of N=2+: |current_day| plus |N-1| more days should be counted.
uint32_t oldest_accepted_day = current_day - event_config.window + 1;
// Cap |oldest_accepted_day| to UNIX epoch.
if (event_config.window > current_day)
oldest_accepted_day = 0u;
// Calculate the number of events within the window.
uint32_t event_count = 0;
for (const auto& event_day : event->events()) {
if (event_day.day() < oldest_accepted_day)
continue;
event_count += event_day.count();
}
return event_config.comparator.MeetsCriteria(event_count);
}
bool FeatureConfigConditionValidator::AvailabilityMeetsConditions(
const base::Feature& feature,
Comparator comparator,
const AvailabilityModel& availability_model,
uint32_t current_day) const {
if (comparator.type == ANY)
return true;
base::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);
}
} // namespace feature_engagement