1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
|
// Copyright 2014 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/variations/study_filtering.h"
#include <set>
namespace variations {
namespace {
Study_Platform GetCurrentPlatform() {
#if defined(OS_WIN)
return Study_Platform_PLATFORM_WINDOWS;
#elif defined(OS_IOS)
return Study_Platform_PLATFORM_IOS;
#elif defined(OS_MACOSX)
return Study_Platform_PLATFORM_MAC;
#elif defined(OS_CHROMEOS)
return Study_Platform_PLATFORM_CHROMEOS;
#elif defined(OS_ANDROID)
return Study_Platform_PLATFORM_ANDROID;
#elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
// Default BSD and SOLARIS to Linux to not break those builds, although these
// platforms are not officially supported by Chrome.
return Study_Platform_PLATFORM_LINUX;
#else
#error Unknown platform
#endif
}
// Converts |date_time| in Study date format to base::Time.
base::Time ConvertStudyDateToBaseTime(int64 date_time) {
return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time);
}
} // namespace
namespace internal {
bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel) {
// An empty channel list matches all channels.
if (filter.channel_size() == 0)
return true;
for (int i = 0; i < filter.channel_size(); ++i) {
if (filter.channel(i) == channel)
return true;
}
return false;
}
bool CheckStudyFormFactor(const Study_Filter& filter,
Study_FormFactor form_factor) {
// An empty form factor list matches all form factors.
if (filter.form_factor_size() == 0)
return true;
for (int i = 0; i < filter.form_factor_size(); ++i) {
if (filter.form_factor(i) == form_factor)
return true;
}
return false;
}
bool CheckStudyHardwareClass(const Study_Filter& filter,
const std::string& hardware_class) {
// Empty hardware_class and exclude_hardware_class matches all.
if (filter.hardware_class_size() == 0 &&
filter.exclude_hardware_class_size() == 0) {
return true;
}
// Checks if we are supposed to filter for a specified set of
// hardware_classes. Note that this means this overrides the
// exclude_hardware_class in case that ever occurs (which it shouldn't).
if (filter.hardware_class_size() > 0) {
for (int i = 0; i < filter.hardware_class_size(); ++i) {
// Check if the entry is a substring of |hardware_class|.
size_t position = hardware_class.find(filter.hardware_class(i));
if (position != std::string::npos)
return true;
}
// None of the requested hardware_classes match.
return false;
}
// Omit if matches any of the exclude entries.
for (int i = 0; i < filter.exclude_hardware_class_size(); ++i) {
// Check if the entry is a substring of |hardware_class|.
size_t position = hardware_class.find(
filter.exclude_hardware_class(i));
if (position != std::string::npos)
return false;
}
// None of the exclusions match, so this accepts.
return true;
}
bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale) {
// An empty locale list matches all locales.
if (filter.locale_size() == 0)
return true;
for (int i = 0; i < filter.locale_size(); ++i) {
if (filter.locale(i) == locale)
return true;
}
return false;
}
bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform) {
// An empty platform list matches all platforms.
if (filter.platform_size() == 0)
return true;
for (int i = 0; i < filter.platform_size(); ++i) {
if (filter.platform(i) == platform)
return true;
}
return false;
}
bool CheckStudyStartDate(const Study_Filter& filter,
const base::Time& date_time) {
if (filter.has_start_date()) {
const base::Time start_date =
ConvertStudyDateToBaseTime(filter.start_date());
return date_time >= start_date;
}
return true;
}
bool CheckStudyVersion(const Study_Filter& filter,
const base::Version& version) {
if (filter.has_min_version()) {
if (version.CompareToWildcardString(filter.min_version()) < 0)
return false;
}
if (filter.has_max_version()) {
if (version.CompareToWildcardString(filter.max_version()) > 0)
return false;
}
return true;
}
bool IsStudyExpired(const Study& study, const base::Time& date_time) {
if (study.has_expiry_date()) {
const base::Time expiry_date =
ConvertStudyDateToBaseTime(study.expiry_date());
return date_time >= expiry_date;
}
return false;
}
bool ShouldAddStudy(
const Study& study,
const std::string& locale,
const base::Time& reference_date,
const base::Version& version,
Study_Channel channel,
Study_FormFactor form_factor,
const std::string& hardware_class) {
if (study.has_filter()) {
if (!CheckStudyChannel(study.filter(), channel)) {
DVLOG(1) << "Filtered out study " << study.name() << " due to channel.";
return false;
}
if (!CheckStudyFormFactor(study.filter(), form_factor)) {
DVLOG(1) << "Filtered out study " << study.name() <<
" due to form factor.";
return false;
}
if (!CheckStudyLocale(study.filter(), locale)) {
DVLOG(1) << "Filtered out study " << study.name() << " due to locale.";
return false;
}
if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) {
DVLOG(1) << "Filtered out study " << study.name() << " due to platform.";
return false;
}
if (!CheckStudyVersion(study.filter(), version)) {
DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
return false;
}
if (!CheckStudyStartDate(study.filter(), reference_date)) {
DVLOG(1) << "Filtered out study " << study.name() <<
" due to start date.";
return false;
}
if (!CheckStudyHardwareClass(study.filter(), hardware_class)) {
DVLOG(1) << "Filtered out study " << study.name() <<
" due to hardware_class.";
return false;
}
}
DVLOG(1) << "Kept study " << study.name() << ".";
return true;
}
} // namespace internal
void FilterAndValidateStudies(
const VariationsSeed& seed,
const std::string& locale,
const base::Time& reference_date,
const base::Version& version,
Study_Channel channel,
Study_FormFactor form_factor,
const std::string& hardware_class,
std::vector<ProcessedStudy>* filtered_studies) {
DCHECK(version.IsValid());
// Add expired studies (in a disabled state) only after all the non-expired
// studies have been added (and do not add an expired study if a corresponding
// non-expired study got added). This way, if there's both an expired and a
// non-expired study that applies, the non-expired study takes priority.
std::set<std::string> created_studies;
std::vector<const Study*> expired_studies;
for (int i = 0; i < seed.study_size(); ++i) {
const Study& study = seed.study(i);
if (!internal::ShouldAddStudy(study, locale, reference_date, version,
channel, form_factor, hardware_class)) {
continue;
}
if (internal::IsStudyExpired(study, reference_date)) {
expired_studies.push_back(&study);
} else if (!ContainsKey(created_studies, study.name())) {
ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies);
created_studies.insert(study.name());
}
}
for (size_t i = 0; i < expired_studies.size(); ++i) {
if (!ContainsKey(created_studies, expired_studies[i]->name())) {
ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true,
filtered_studies);
}
}
}
} // namespace variations
|