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"
13 Study_Platform
GetCurrentPlatform() {
15 return Study_Platform_PLATFORM_WINDOWS
;
17 return Study_Platform_PLATFORM_IOS
;
18 #elif defined(OS_MACOSX)
19 return Study_Platform_PLATFORM_MAC
;
20 #elif defined(OS_CHROMEOS)
21 return Study_Platform_PLATFORM_CHROMEOS
;
22 #elif defined(OS_ANDROID)
23 return Study_Platform_PLATFORM_ANDROID
;
24 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
25 // Default BSD and SOLARIS to Linux to not break those builds, although these
26 // platforms are not officially supported by Chrome.
27 return Study_Platform_PLATFORM_LINUX
;
29 #error Unknown platform
33 // Converts |date_time| in Study date format to base::Time.
34 base::Time
ConvertStudyDateToBaseTime(int64 date_time
) {
35 return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time
);
42 bool CheckStudyChannel(const Study_Filter
& filter
, Study_Channel channel
) {
43 // An empty channel list matches all channels.
44 if (filter
.channel_size() == 0)
47 for (int i
= 0; i
< filter
.channel_size(); ++i
) {
48 if (filter
.channel(i
) == channel
)
54 bool CheckStudyFormFactor(const Study_Filter
& filter
,
55 Study_FormFactor form_factor
) {
56 // An empty form factor list matches all form factors.
57 if (filter
.form_factor_size() == 0)
60 for (int i
= 0; i
< filter
.form_factor_size(); ++i
) {
61 if (filter
.form_factor(i
) == form_factor
)
67 bool CheckStudyHardwareClass(const Study_Filter
& filter
,
68 const std::string
& hardware_class
) {
69 // Empty hardware_class and exclude_hardware_class matches all.
70 if (filter
.hardware_class_size() == 0 &&
71 filter
.exclude_hardware_class_size() == 0) {
75 // Checks if we are supposed to filter for a specified set of
76 // hardware_classes. Note that this means this overrides the
77 // exclude_hardware_class in case that ever occurs (which it shouldn't).
78 if (filter
.hardware_class_size() > 0) {
79 for (int i
= 0; i
< filter
.hardware_class_size(); ++i
) {
80 // Check if the entry is a substring of |hardware_class|.
81 size_t position
= hardware_class
.find(filter
.hardware_class(i
));
82 if (position
!= std::string::npos
)
85 // None of the requested hardware_classes match.
89 // Omit if matches any of the exclude entries.
90 for (int i
= 0; i
< filter
.exclude_hardware_class_size(); ++i
) {
91 // Check if the entry is a substring of |hardware_class|.
92 size_t position
= hardware_class
.find(
93 filter
.exclude_hardware_class(i
));
94 if (position
!= std::string::npos
)
98 // None of the exclusions match, so this accepts.
102 bool CheckStudyLocale(const Study_Filter
& filter
, const std::string
& locale
) {
103 // An empty locale list matches all locales.
104 if (filter
.locale_size() == 0)
107 for (int i
= 0; i
< filter
.locale_size(); ++i
) {
108 if (filter
.locale(i
) == locale
)
114 bool CheckStudyPlatform(const Study_Filter
& filter
, Study_Platform platform
) {
115 // An empty platform list matches all platforms.
116 if (filter
.platform_size() == 0)
119 for (int i
= 0; i
< filter
.platform_size(); ++i
) {
120 if (filter
.platform(i
) == platform
)
126 bool CheckStudyStartDate(const Study_Filter
& filter
,
127 const base::Time
& date_time
) {
128 if (filter
.has_start_date()) {
129 const base::Time start_date
=
130 ConvertStudyDateToBaseTime(filter
.start_date());
131 return date_time
>= start_date
;
137 bool CheckStudyVersion(const Study_Filter
& filter
,
138 const base::Version
& version
) {
139 if (filter
.has_min_version()) {
140 if (version
.CompareToWildcardString(filter
.min_version()) < 0)
144 if (filter
.has_max_version()) {
145 if (version
.CompareToWildcardString(filter
.max_version()) > 0)
152 bool IsStudyExpired(const Study
& study
, const base::Time
& date_time
) {
153 if (study
.has_expiry_date()) {
154 const base::Time expiry_date
=
155 ConvertStudyDateToBaseTime(study
.expiry_date());
156 return date_time
>= expiry_date
;
164 const std::string
& locale
,
165 const base::Time
& reference_date
,
166 const base::Version
& version
,
167 Study_Channel channel
,
168 Study_FormFactor form_factor
,
169 const std::string
& hardware_class
) {
170 if (study
.has_filter()) {
171 if (!CheckStudyChannel(study
.filter(), channel
)) {
172 DVLOG(1) << "Filtered out study " << study
.name() << " due to channel.";
176 if (!CheckStudyFormFactor(study
.filter(), form_factor
)) {
177 DVLOG(1) << "Filtered out study " << study
.name() <<
178 " due to form factor.";
182 if (!CheckStudyLocale(study
.filter(), locale
)) {
183 DVLOG(1) << "Filtered out study " << study
.name() << " due to locale.";
187 if (!CheckStudyPlatform(study
.filter(), GetCurrentPlatform())) {
188 DVLOG(1) << "Filtered out study " << study
.name() << " due to platform.";
192 if (!CheckStudyVersion(study
.filter(), version
)) {
193 DVLOG(1) << "Filtered out study " << study
.name() << " due to version.";
197 if (!CheckStudyStartDate(study
.filter(), reference_date
)) {
198 DVLOG(1) << "Filtered out study " << study
.name() <<
199 " due to start date.";
203 if (!CheckStudyHardwareClass(study
.filter(), hardware_class
)) {
204 DVLOG(1) << "Filtered out study " << study
.name() <<
205 " due to hardware_class.";
210 DVLOG(1) << "Kept study " << study
.name() << ".";
214 } // namespace internal
216 void FilterAndValidateStudies(
217 const VariationsSeed
& seed
,
218 const std::string
& locale
,
219 const base::Time
& reference_date
,
220 const base::Version
& version
,
221 Study_Channel channel
,
222 Study_FormFactor form_factor
,
223 const std::string
& hardware_class
,
224 std::vector
<ProcessedStudy
>* filtered_studies
) {
225 DCHECK(version
.IsValid());
227 // Add expired studies (in a disabled state) only after all the non-expired
228 // studies have been added (and do not add an expired study if a corresponding
229 // non-expired study got added). This way, if there's both an expired and a
230 // non-expired study that applies, the non-expired study takes priority.
231 std::set
<std::string
> created_studies
;
232 std::vector
<const Study
*> expired_studies
;
234 for (int i
= 0; i
< seed
.study_size(); ++i
) {
235 const Study
& study
= seed
.study(i
);
236 if (!internal::ShouldAddStudy(study
, locale
, reference_date
, version
,
237 channel
, form_factor
, hardware_class
)) {
241 if (internal::IsStudyExpired(study
, reference_date
)) {
242 expired_studies
.push_back(&study
);
243 } else if (!ContainsKey(created_studies
, study
.name())) {
244 ProcessedStudy::ValidateAndAppendStudy(&study
, false, filtered_studies
);
245 created_studies
.insert(study
.name());
249 for (size_t i
= 0; i
< expired_studies
.size(); ++i
) {
250 if (!ContainsKey(created_studies
, expired_studies
[i
]->name())) {
251 ProcessedStudy::ValidateAndAppendStudy(expired_studies
[i
], true,
257 } // namespace variations