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 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
|
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/base/cast_features.h"
#include <algorithm>
#include <utility>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
namespace chromecast {
namespace {
// The name of the default group to use for Cast DCS features.
const char kDefaultDCSFeaturesGroup[] = "default_dcs_features_group";
std::unordered_set<int32_t>& GetExperimentIds() {
static base::NoDestructor<std::unordered_set<int32_t>> g_experiment_ids;
return *g_experiment_ids;
}
bool g_experiment_ids_initialized = false;
// The collection of features that have been registered by unit tests
std::vector<const base::Feature*>& GetTestFeatures() {
static base::NoDestructor<std::vector<const base::Feature*>>
features_for_test;
return *features_for_test;
}
void SetExperimentIds(const base::Value::List& list) {
DCHECK(!g_experiment_ids_initialized);
std::unordered_set<int32_t> ids;
for (const auto& it : list) {
if (it.is_int()) {
ids.insert(it.GetInt());
} else {
LOG(ERROR) << "Non-integer value found in experiment id list!";
}
}
GetExperimentIds().swap(ids);
g_experiment_ids_initialized = true;
}
} // namespace
// PLEASE READ!
// Cast Platform Features are listed below. These features may be
// toggled via configs fetched from DCS for devices in the field, or via
// command-line flags set by the developer. For the end-to-end details of the
// system design, please see go/dcs-experiments.
//
// Below are useful steps on how to use these features in your code.
//
// 1) Declaring and defining the feature.
// All Cast Platform Features should be declared in a common file with other
// Cast Platform Features (ex. chromecast/base/cast_features.h). When
// defining your feature, you will need to assign a default value. This is
// the value that the feature will hold until overriden by the server or the
// command line. Here's an exmaple:
//
// BASE_FEATURE(kSuperSecretSauce, "SuperSecretSauce",
// base::FEATURE_DISABLED_BY_DEFAULT);
//
// IMPORTANT NOTE:
// The first parameter that you pass in the definition is the feature's name.
// This MUST match the DCS experiment key for this feature.
//
// While Features elsewhere in Chromium alternatively use dashed-case or
// PascalCase for their names, Chromecast features should use snake_case
// (lowercase letters separated by underscores). This will ensure that DCS
// configs, which are passed around as JSON, remain conformant and readable.
//
// 2) Using the feature in client code.
// Using these features in your code is easy. Here's an example:
//
// #include “base/feature_list.h”
// #include “chromecast/base/chromecast_switches.h”
//
// std::unique_ptr<Foo> CreateFoo() {
// if (base::FeatureList::IsEnabled(kSuperSecretSauce))
// return std::make_unique<SuperSecretFoo>();
// return std::make_unique<BoringOldFoo>();
// }
//
// base::FeatureList can be called from any thread, in any process, at any
// time after PreCreateThreads(). It will return whether the feature is
// enabled.
//
// 3) Overriding the default value from the server.
// For devices in the field, DCS will issue different configs to different
// groups of devices, allowing us to run experiments on features. These
// feature settings will manifest on the next boot of cast_shell. In the
// example, if the latest config for a particular device set the value of
// kSuperSecretSauce to true, the appropriate code path would be taken.
// Otherwise, the default value, false, would be used. For more details on
// setting up experiments, see go/dcs-launch.
//
// 4) Overriding the default and server values from the command-line.
// While the server value trumps the default values, the command line trumps
// both. Enable features by passing this command line arg to cast_shell:
//
// --enable-features=enable_foo,enable_super_secret_sauce
//
// Features are separated by commas. Disable features by passing:
//
// --disable-features=enable_foo,enable_bar
//
// 5) If you add a new feature to the system you must include it in kFeatures
// This is because the system relies on knowing all of the features so
// it can properly iterate over all features to detect changes.
//
// Begin Chromecast Feature definitions.
// Allows applications to access media capture devices (webcams/microphones)
// through getUserMedia API.
BASE_FEATURE(kAllowUserMediaAccess,
"allow_user_media_access",
base::FEATURE_DISABLED_BY_DEFAULT);
// Enables the use of QUIC in Cast-specific NetworkContexts. See
// chromecast/browser/cast_network_contexts.cc for usage.
BASE_FEATURE(kEnableQuic, "enable_quic", base::FEATURE_DISABLED_BY_DEFAULT);
// Enables triple-buffer 720p graphics (overriding default graphics buffer
// settings for a platform).
BASE_FEATURE(kTripleBuffer720,
"enable_triple_buffer_720",
base::FEATURE_DISABLED_BY_DEFAULT);
// Enables single-buffered graphics (overriding default graphics buffer
// settings and takes precedence over triple-buffer feature).
BASE_FEATURE(kSingleBuffer,
"enable_single_buffer",
base::FEATURE_DISABLED_BY_DEFAULT);
// Disable idle sockets closing on memory pressure. See
// chromecast/browser/cast_network_contexts.cc for usage.
BASE_FEATURE(kDisableIdleSocketsCloseOnMemoryPressure,
"disable_idle_sockets_close_on_memory_pressure",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kEnableGeneralAudienceBrowsing,
"enable_general_audience_browsing",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kEnableSideGesturePassThrough,
"enable_side_gesture_pass_through",
base::FEATURE_DISABLED_BY_DEFAULT);
// Uses AudioManagerAndroid, instead of CastAudioManagerAndroid. This will
// disable lots of Cast features, so it should only be used for development and
// testing.
BASE_FEATURE(kEnableChromeAudioManagerAndroid,
"enable_chrome_audio_manager_android",
base::FEATURE_DISABLED_BY_DEFAULT);
// Enables CastAudioOutputDevice for audio output on Android. When disabled,
// CastAudioManagerAndroid will be used.
BASE_FEATURE(kEnableCastAudioOutputDevice,
"enable_cast_audio_output_device",
base::FEATURE_DISABLED_BY_DEFAULT);
// End Chromecast Feature definitions.
const base::Feature* kFeatures[] = {
&kAllowUserMediaAccess,
&kEnableQuic,
&kTripleBuffer720,
&kSingleBuffer,
&kDisableIdleSocketsCloseOnMemoryPressure,
&kEnableGeneralAudienceBrowsing,
&kEnableSideGesturePassThrough,
&kEnableChromeAudioManagerAndroid,
&kEnableCastAudioOutputDevice,
};
std::vector<const base::Feature*> GetInternalFeatures();
const std::vector<const base::Feature*>& GetFeatures() {
static const base::NoDestructor<std::vector<const base::Feature*>> features(
[] {
std::vector<const base::Feature*> features(std::begin(kFeatures),
std::end(kFeatures));
auto internal_features = GetInternalFeatures();
features.insert(features.end(), internal_features.begin(),
internal_features.end());
return features;
}());
if (GetTestFeatures().size() > 0)
return GetTestFeatures();
return *features;
}
void InitializeFeatureList(const base::Value::Dict& dcs_features,
const base::Value::List& dcs_experiment_ids,
const std::string& cmd_line_enable_features,
const std::string& cmd_line_disable_features,
const std::string& extra_enable_features,
const std::string& extra_disable_features) {
DCHECK(!base::FeatureList::GetInstance());
// Set the experiments.
SetExperimentIds(dcs_experiment_ids);
std::string all_enable_features =
cmd_line_enable_features + "," + extra_enable_features;
std::string all_disable_features =
cmd_line_disable_features + "," + extra_disable_features;
// Initialize the FeatureList from the command line.
auto feature_list = std::make_unique<base::FeatureList>();
feature_list->InitializeFromCommandLine(all_enable_features,
all_disable_features);
// Override defaults from the DCS config.
for (const auto kv : dcs_features) {
// Each feature must have its own FieldTrial object. Since experiments are
// controlled server-side for Chromecast, and this class is designed with a
// client-side experimentation framework in mind, these parameters are
// carefully chosen:
// - The field trial name is unused for our purposes. However, we need to
// maintain a 1:1 mapping with Features in order to properly store and
// access parameters associated with each Feature. Therefore, use the
// Feature's name as the FieldTrial name to ensure uniqueness.
// - We don't care about the group_id.
//
const std::string& feature_name = kv.first;
auto* field_trial = base::FieldTrialList::CreateFieldTrial(
feature_name, kDefaultDCSFeaturesGroup);
// |field_trial| is null only if the trial has already been forced to
// another group. This shouldn't happen, unless we've processed a
// --force-fieldtrial commandline argument that overrides this to some other
// group.
if (!field_trial) {
LOG(ERROR) << "A trial was already created for a DCS feature: "
<< feature_name;
continue;
}
if (kv.second.is_bool()) {
// A boolean entry simply either enables or disables a feature.
feature_list->RegisterFieldTrialOverride(
feature_name,
kv.second.GetBool() ? base::FeatureList::OVERRIDE_ENABLE_FEATURE
: base::FeatureList::OVERRIDE_DISABLE_FEATURE,
field_trial);
continue;
}
if (kv.second.is_dict()) {
// A dictionary entry implies that the feature is enabled.
feature_list->RegisterFieldTrialOverride(
feature_name, base::FeatureList::OVERRIDE_ENABLE_FEATURE,
field_trial);
// If the feature has not been overriden from the command line, set its
// parameters accordingly.
if (!feature_list->IsFeatureOverriddenFromCommandLine(
feature_name, base::FeatureList::OVERRIDE_DISABLE_FEATURE)) {
// Build a map of the FieldTrial parameters and associate it to the
// FieldTrial.
base::FieldTrialParams params;
for (const auto params_kv : kv.second.GetDict()) {
if (params_kv.second.is_string()) {
params[params_kv.first] = params_kv.second.GetString();
} else {
LOG(ERROR) << "Entry in params dict for \"" << feature_name << "\""
<< " feature is not a string. Skipping.";
}
}
// Register the params, so that they can be queried by client code.
bool success = base::AssociateFieldTrialParams(
feature_name, kDefaultDCSFeaturesGroup, params);
DCHECK(success);
}
continue;
}
// Other base::Value types are not supported.
LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
<< feature_name << " type: " << kv.second.type();
}
base::FeatureList::SetInstance(std::move(feature_list));
}
bool IsFeatureEnabled(const base::Feature& feature) {
DCHECK(base::Contains(GetFeatures(), &feature)) << feature.name;
return base::FeatureList::IsEnabled(feature);
}
base::Value::Dict GetOverriddenFeaturesForStorage(
const base::Value::Dict& features) {
base::Value::Dict persistent_dict;
// |features| maps feature names to either a boolean or a dict of params.
for (const auto feature : features) {
if (feature.second.is_bool()) {
persistent_dict.Set(feature.first, feature.second.GetBool());
continue;
}
if (feature.second.is_dict()) {
const base::Value* params_dict = &feature.second;
base::Value::Dict params;
for (const auto [param_key, param_val] : params_dict->GetDict()) {
if (param_val.is_bool()) {
params.Set(param_key, param_val.GetBool() ? "true" : "false");
} else if (param_val.is_int()) {
params.Set(param_key, base::NumberToString(param_val.GetInt()));
} else if (param_val.is_double()) {
params.Set(param_key, base::NumberToString(param_val.GetDouble()));
} else if (param_val.is_string()) {
params.Set(param_key, param_val.GetString());
} else {
LOG(ERROR) << "Entry in params dict for \"" << feature.first << "\""
<< " is not of a supported type (key: " << param_key
<< ", type: " << param_val.type();
}
}
persistent_dict.Set(feature.first, std::move(params));
continue;
}
// Other base::Value types are not supported.
LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
<< feature.first << " type: " << feature.second.type();
}
return persistent_dict;
}
const std::unordered_set<int32_t>& GetDCSExperimentIds() {
DCHECK(g_experiment_ids_initialized);
return GetExperimentIds();
}
void ResetCastFeaturesForTesting() {
g_experiment_ids_initialized = false;
base::FeatureList::ClearInstanceForTesting();
GetTestFeatures().clear();
}
void SetFeaturesForTest(std::vector<const base::Feature*> features) {
GetTestFeatures() = std::move(features);
}
} // namespace chromecast
|