1 // Copyright 2014 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.
5 #include "components/variations/study_filtering.h"
9 #include "base/stl_util.h"
11 namespace variations
{
15 Study_Platform
GetCurrentPlatform() {
17 return Study_Platform_PLATFORM_WINDOWS
;
19 return Study_Platform_PLATFORM_IOS
;
20 #elif defined(OS_MACOSX)
21 return Study_Platform_PLATFORM_MAC
;
22 #elif defined(OS_CHROMEOS)
23 return Study_Platform_PLATFORM_CHROMEOS
;
24 #elif defined(OS_ANDROID)
25 return Study_Platform_PLATFORM_ANDROID
;
26 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
27 // Default BSD and SOLARIS to Linux to not break those builds, although these
28 // platforms are not officially supported by Chrome.
29 return Study_Platform_PLATFORM_LINUX
;
31 #error Unknown platform
35 // Converts |date_time| in Study date format to base::Time.
36 base::Time
ConvertStudyDateToBaseTime(int64 date_time
) {
37 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time
);
44 bool CheckStudyChannel(const Study_Filter
& filter
, Study_Channel channel
) {
45 // An empty channel list matches all channels.
46 if (filter
.channel_size() == 0)
49 for (int i
= 0; i
< filter
.channel_size(); ++i
) {
50 if (filter
.channel(i
) == channel
)
56 bool CheckStudyFormFactor(const Study_Filter
& filter
,
57 Study_FormFactor form_factor
) {
58 // An empty form factor list matches all form factors.
59 if (filter
.form_factor_size() == 0)
62 for (int i
= 0; i
< filter
.form_factor_size(); ++i
) {
63 if (filter
.form_factor(i
) == form_factor
)
69 bool CheckStudyHardwareClass(const Study_Filter
& filter
,
70 const std::string
& hardware_class
) {
71 // Empty hardware_class and exclude_hardware_class matches all.
72 if (filter
.hardware_class_size() == 0 &&
73 filter
.exclude_hardware_class_size() == 0) {
77 // Checks if we are supposed to filter for a specified set of
78 // hardware_classes. Note that this means this overrides the
79 // exclude_hardware_class in case that ever occurs (which it shouldn't).
80 if (filter
.hardware_class_size() > 0) {
81 for (int i
= 0; i
< filter
.hardware_class_size(); ++i
) {
82 // Check if the entry is a substring of |hardware_class|.
83 size_t position
= hardware_class
.find(filter
.hardware_class(i
));
84 if (position
!= std::string::npos
)
87 // None of the requested hardware_classes match.
91 // Omit if matches any of the exclude entries.
92 for (int i
= 0; i
< filter
.exclude_hardware_class_size(); ++i
) {
93 // Check if the entry is a substring of |hardware_class|.
94 size_t position
= hardware_class
.find(
95 filter
.exclude_hardware_class(i
));
96 if (position
!= std::string::npos
)
100 // None of the exclusions match, so this accepts.
104 bool CheckStudyLocale(const Study_Filter
& filter
, const std::string
& locale
) {
105 // An empty locale list matches all locales.
106 if (filter
.locale_size() == 0)
109 for (int i
= 0; i
< filter
.locale_size(); ++i
) {
110 if (filter
.locale(i
) == locale
)
116 bool CheckStudyPlatform(const Study_Filter
& filter
, Study_Platform platform
) {
117 // An empty platform list matches all platforms.
118 if (filter
.platform_size() == 0)
121 for (int i
= 0; i
< filter
.platform_size(); ++i
) {
122 if (filter
.platform(i
) == platform
)
128 bool CheckStudyStartDate(const Study_Filter
& filter
,
129 const base::Time
& date_time
) {
130 if (filter
.has_start_date()) {
131 const base::Time start_date
=
132 ConvertStudyDateToBaseTime(filter
.start_date());
133 return date_time
>= start_date
;
139 bool CheckStudyVersion(const Study_Filter
& filter
,
140 const base::Version
& version
) {
141 if (filter
.has_min_version()) {
142 if (version
.CompareToWildcardString(filter
.min_version()) < 0)
146 if (filter
.has_max_version()) {
147 if (version
.CompareToWildcardString(filter
.max_version()) > 0)
154 bool CheckStudyCountry(const Study_Filter
& filter
, const std::string
& country
) {
155 // Empty country and exclude_country matches all.
156 if (filter
.country_size() == 0 && filter
.exclude_country_size() == 0)
159 // Checks if we are supposed to filter for a specified set of countries. Note
160 // that this means this overrides the exclude_country in case that ever occurs
161 // (which it shouldn't).
162 if (filter
.country_size() > 0)
163 return ContainsValue(filter
.country(), country
);
165 // Omit if matches any of the exclude entries.
166 return !ContainsValue(filter
.exclude_country(), country
);
169 bool IsStudyExpired(const Study
& study
, const base::Time
& date_time
) {
170 if (study
.has_expiry_date()) {
171 const base::Time expiry_date
=
172 ConvertStudyDateToBaseTime(study
.expiry_date());
173 return date_time
>= expiry_date
;
181 const std::string
& locale
,
182 const base::Time
& reference_date
,
183 const base::Version
& version
,
184 Study_Channel channel
,
185 Study_FormFactor form_factor
,
186 const std::string
& hardware_class
,
187 const std::string
& country
) {
188 if (study
.has_filter()) {
189 if (!CheckStudyChannel(study
.filter(), channel
)) {
190 DVLOG(1) << "Filtered out study " << study
.name() << " due to channel.";
194 if (!CheckStudyFormFactor(study
.filter(), form_factor
)) {
195 DVLOG(1) << "Filtered out study " << study
.name() <<
196 " due to form factor.";
200 if (!CheckStudyLocale(study
.filter(), locale
)) {
201 DVLOG(1) << "Filtered out study " << study
.name() << " due to locale.";
205 if (!CheckStudyPlatform(study
.filter(), GetCurrentPlatform())) {
206 DVLOG(1) << "Filtered out study " << study
.name() << " due to platform.";
210 if (!CheckStudyVersion(study
.filter(), version
)) {
211 DVLOG(1) << "Filtered out study " << study
.name() << " due to version.";
215 if (!CheckStudyStartDate(study
.filter(), reference_date
)) {
216 DVLOG(1) << "Filtered out study " << study
.name() <<
217 " due to start date.";
221 if (!CheckStudyHardwareClass(study
.filter(), hardware_class
)) {
222 DVLOG(1) << "Filtered out study " << study
.name() <<
223 " due to hardware_class.";
227 if (!CheckStudyCountry(study
.filter(), country
)) {
228 DVLOG(1) << "Filtered out study " << study
.name() << " due to country.";
233 DVLOG(1) << "Kept study " << study
.name() << ".";
237 } // namespace internal
239 void FilterAndValidateStudies(const VariationsSeed
& seed
,
240 const std::string
& locale
,
241 const base::Time
& reference_date
,
242 const base::Version
& version
,
243 Study_Channel channel
,
244 Study_FormFactor form_factor
,
245 const std::string
& hardware_class
,
246 const std::string
& session_consistency_country
,
247 const std::string
& permanent_consistency_country
,
248 std::vector
<ProcessedStudy
>* filtered_studies
) {
249 DCHECK(version
.IsValid());
251 // Add expired studies (in a disabled state) only after all the non-expired
252 // studies have been added (and do not add an expired study if a corresponding
253 // non-expired study got added). This way, if there's both an expired and a
254 // non-expired study that applies, the non-expired study takes priority.
255 std::set
<std::string
> created_studies
;
256 std::vector
<const Study
*> expired_studies
;
258 for (int i
= 0; i
< seed
.study_size(); ++i
) {
259 const Study
& study
= seed
.study(i
);
261 // Unless otherwise specified, use an empty country that won't pass any
262 // filters that specifically include countries, but will pass any filters
263 // that specifically exclude countries.
265 switch (study
.consistency()) {
266 case Study_Consistency_SESSION
:
267 country
= session_consistency_country
;
269 case Study_Consistency_PERMANENT
:
270 // Use the saved |permanent_consistency_country| for permanent
271 // consistency studies. This allows Chrome to use the same country for
272 // filtering permanent consistency studies between Chrome upgrades.
273 // Since some studies have user-visible effects, this helps to avoid
274 // annoying users with experimental group churn while traveling.
275 country
= permanent_consistency_country
;
279 if (!internal::ShouldAddStudy(study
, locale
, reference_date
, version
,
280 channel
, form_factor
, hardware_class
,
285 if (internal::IsStudyExpired(study
, reference_date
)) {
286 expired_studies
.push_back(&study
);
287 } else if (!ContainsKey(created_studies
, study
.name())) {
288 ProcessedStudy::ValidateAndAppendStudy(&study
, false, filtered_studies
);
289 created_studies
.insert(study
.name());
293 for (size_t i
= 0; i
< expired_studies
.size(); ++i
) {
294 if (!ContainsKey(created_studies
, expired_studies
[i
]->name())) {
295 ProcessedStudy::ValidateAndAppendStudy(expired_studies
[i
], true,
301 } // namespace variations