1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/feature_engagement/internal/feature_config_condition_validator.h"
6 
7 #include <algorithm>
8 #include <string>
9 #include <vector>
10 
11 #include "base/check.h"
12 #include "base/feature_list.h"
13 #include "base/notreached.h"
14 #include "base/stl_util.h"
15 #include "components/feature_engagement/internal/availability_model.h"
16 #include "components/feature_engagement/internal/display_lock_controller.h"
17 #include "components/feature_engagement/internal/event_model.h"
18 #include "components/feature_engagement/internal/proto/feature_event.pb.h"
19 #include "components/feature_engagement/public/configuration.h"
20 #include "components/feature_engagement/public/feature_list.h"
21 
22 namespace feature_engagement {
23 
FeatureConfigConditionValidator()24 FeatureConfigConditionValidator::FeatureConfigConditionValidator()
25     : currently_showing_(false) {}
26 
27 FeatureConfigConditionValidator::~FeatureConfigConditionValidator() = default;
28 
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) const29 ConditionValidator::Result FeatureConfigConditionValidator::MeetsConditions(
30     const base::Feature& feature,
31     const FeatureConfig& config,
32     const EventModel& event_model,
33     const AvailabilityModel& availability_model,
34     const DisplayLockController& display_lock_controller,
35     uint32_t current_day) const {
36   ConditionValidator::Result result(true);
37   result.event_model_ready_ok = event_model.IsReady();
38   result.currently_showing_ok = !currently_showing_;
39   result.feature_enabled_ok = base::FeatureList::IsEnabled(feature);
40   result.config_ok = config.valid;
41   result.used_ok =
42       EventConfigMeetsConditions(config.used, event_model, current_day);
43   result.trigger_ok =
44       EventConfigMeetsConditions(config.trigger, event_model, current_day);
45 
46   for (const auto& event_config : config.event_configs) {
47     result.preconditions_ok &=
48         EventConfigMeetsConditions(event_config, event_model, current_day);
49   }
50 
51   result.session_rate_ok =
52       SessionRateMeetsConditions(config.session_rate, feature);
53 
54   result.availability_model_ready_ok = availability_model.IsReady();
55 
56   result.availability_ok = AvailabilityMeetsConditions(
57       feature, config.availability, availability_model, current_day);
58 
59   result.display_lock_ok = !display_lock_controller.IsDisplayLocked();
60 
61   return result;
62 }
63 
NotifyIsShowing(const base::Feature & feature,const FeatureConfig & config,const std::vector<std::string> & all_feature_names)64 void FeatureConfigConditionValidator::NotifyIsShowing(
65     const base::Feature& feature,
66     const FeatureConfig& config,
67     const std::vector<std::string>& all_feature_names) {
68   DCHECK(!currently_showing_);
69   DCHECK(base::FeatureList::IsEnabled(feature));
70 
71   currently_showing_ = true;
72 
73   switch (config.session_rate_impact.type) {
74     case SessionRateImpact::Type::ALL:
75       for (const std::string& feature_name : all_feature_names)
76         ++times_shown_for_feature_[feature_name];
77       break;
78     case SessionRateImpact::Type::NONE:
79       // Intentionally ignore, since no features should be impacted.
80       break;
81     case SessionRateImpact::Type::EXPLICIT:
82       DCHECK(config.session_rate_impact.affected_features.has_value());
83       for (const std::string& feature_name :
84            config.session_rate_impact.affected_features.value()) {
85         DCHECK(base::Contains(all_feature_names, feature_name));
86         ++times_shown_for_feature_[feature_name];
87       }
88       break;
89     default:
90       // All cases should be covered.
91       NOTREACHED();
92   }
93 }
94 
NotifyDismissed(const base::Feature & feature)95 void FeatureConfigConditionValidator::NotifyDismissed(
96     const base::Feature& feature) {
97   currently_showing_ = false;
98 }
99 
EventConfigMeetsConditions(const EventConfig & event_config,const EventModel & event_model,uint32_t current_day) const100 bool FeatureConfigConditionValidator::EventConfigMeetsConditions(
101     const EventConfig& event_config,
102     const EventModel& event_model,
103     uint32_t current_day) const {
104   uint32_t event_count = event_model.GetEventCount(
105       event_config.name, current_day, event_config.window);
106   return event_config.comparator.MeetsCriteria(event_count);
107 }
108 
AvailabilityMeetsConditions(const base::Feature & feature,Comparator comparator,const AvailabilityModel & availability_model,uint32_t current_day) const109 bool FeatureConfigConditionValidator::AvailabilityMeetsConditions(
110     const base::Feature& feature,
111     Comparator comparator,
112     const AvailabilityModel& availability_model,
113     uint32_t current_day) const {
114   if (comparator.type == ANY)
115     return true;
116 
117   base::Optional<uint32_t> availability_day =
118       availability_model.GetAvailability(feature);
119   if (!availability_day.has_value())
120     return false;
121 
122   uint32_t days_available = current_day - availability_day.value();
123 
124   // Ensure that availability days never wrap around.
125   if (availability_day.value() > current_day)
126     days_available = 0u;
127 
128   return comparator.MeetsCriteria(days_available);
129 }
130 
SessionRateMeetsConditions(const Comparator session_rate,const base::Feature & feature) const131 bool FeatureConfigConditionValidator::SessionRateMeetsConditions(
132     const Comparator session_rate,
133     const base::Feature& feature) const {
134   const auto it = times_shown_for_feature_.find(feature.name);
135   if (it == times_shown_for_feature_.end())
136     return session_rate.MeetsCriteria(0u);
137   return session_rate.MeetsCriteria(it->second);
138 }
139 
140 }  // namespace feature_engagement
141