cast_features.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. #include "chromecast/base/cast_features.h"
  5. #include <algorithm>
  6. #include <utility>
  7. #include "base/command_line.h"
  8. #include "base/containers/contains.h"
  9. #include "base/feature_list.h"
  10. #include "base/metrics/field_trial.h"
  11. #include "base/metrics/field_trial_param_associator.h"
  12. #include "base/metrics/field_trial_params.h"
  13. #include "base/no_destructor.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "base/values.h"
  16. #include "build/build_config.h"
  17. namespace chromecast {
  18. namespace {
  19. // A constant used to always activate a FieldTrial.
  20. const base::FieldTrial::Probability k100PercentProbability = 100;
  21. // The name of the default group to use for Cast DCS features.
  22. const char kDefaultDCSFeaturesGroup[] = "default_dcs_features_group";
  23. std::unordered_set<int32_t>& GetExperimentIds() {
  24. static base::NoDestructor<std::unordered_set<int32_t>> g_experiment_ids;
  25. return *g_experiment_ids;
  26. }
  27. bool g_experiment_ids_initialized = false;
  28. // The collection of features that have been registered by unit tests
  29. std::vector<const base::Feature*>& GetTestFeatures() {
  30. static base::NoDestructor<std::vector<const base::Feature*>>
  31. features_for_test;
  32. return *features_for_test;
  33. }
  34. void SetExperimentIds(const base::Value::List& list) {
  35. DCHECK(!g_experiment_ids_initialized);
  36. std::unordered_set<int32_t> ids;
  37. for (const auto& it : list) {
  38. if (it.is_int()) {
  39. ids.insert(it.GetInt());
  40. } else {
  41. LOG(ERROR) << "Non-integer value found in experiment id list!";
  42. }
  43. }
  44. GetExperimentIds().swap(ids);
  45. g_experiment_ids_initialized = true;
  46. }
  47. } // namespace
  48. // PLEASE READ!
  49. // Cast Platform Features are listed below. These features may be
  50. // toggled via configs fetched from DCS for devices in the field, or via
  51. // command-line flags set by the developer. For the end-to-end details of the
  52. // system design, please see go/dcs-experiments.
  53. //
  54. // Below are useful steps on how to use these features in your code.
  55. //
  56. // 1) Declaring and defining the feature.
  57. // All Cast Platform Features should be declared in a common file with other
  58. // Cast Platform Features (ex. chromecast/base/cast_features.h). When
  59. // defining your feature, you will need to assign a default value. This is
  60. // the value that the feature will hold until overriden by the server or the
  61. // command line. Here's an exmaple:
  62. //
  63. // const base::Feature kSuperSecretSauce{
  64. // "enable_super_secret_sauce", base::FEATURE_DISABLED_BY_DEFAULT};
  65. //
  66. // IMPORTANT NOTE:
  67. // The first parameter that you pass in the definition is the feature's name.
  68. // This MUST match the DCS experiment key for this feature.
  69. //
  70. // While Features elsewhere in Chromium alternatively use dashed-case or
  71. // PascalCase for their names, Chromecast features should use snake_case
  72. // (lowercase letters separated by underscores). This will ensure that DCS
  73. // configs, which are passed around as JSON, remain conformant and readable.
  74. //
  75. // 2) Using the feature in client code.
  76. // Using these features in your code is easy. Here's an example:
  77. //
  78. // #include “base/feature_list.h”
  79. // #include “chromecast/base/chromecast_switches.h”
  80. //
  81. // std::unique_ptr<Foo> CreateFoo() {
  82. // if (base::FeatureList::IsEnabled(kSuperSecretSauce))
  83. // return std::make_unique<SuperSecretFoo>();
  84. // return std::make_unique<BoringOldFoo>();
  85. // }
  86. //
  87. // base::FeatureList can be called from any thread, in any process, at any
  88. // time after PreCreateThreads(). It will return whether the feature is
  89. // enabled.
  90. //
  91. // 3) Overriding the default value from the server.
  92. // For devices in the field, DCS will issue different configs to different
  93. // groups of devices, allowing us to run experiments on features. These
  94. // feature settings will manifest on the next boot of cast_shell. In the
  95. // example, if the latest config for a particular device set the value of
  96. // kSuperSecretSauce to true, the appropriate code path would be taken.
  97. // Otherwise, the default value, false, would be used. For more details on
  98. // setting up experiments, see go/dcs-launch.
  99. //
  100. // 4) Overriding the default and server values from the command-line.
  101. // While the server value trumps the default values, the command line trumps
  102. // both. Enable features by passing this command line arg to cast_shell:
  103. //
  104. // --enable-features=enable_foo,enable_super_secret_sauce
  105. //
  106. // Features are separated by commas. Disable features by passing:
  107. //
  108. // --disable-features=enable_foo,enable_bar
  109. //
  110. // 5) If you add a new feature to the system you must include it in kFeatures
  111. // This is because the system relies on knowing all of the features so
  112. // it can properly iterate over all features to detect changes.
  113. //
  114. // Begin Chromecast Feature definitions.
  115. // Allows applications to access media capture devices (webcams/microphones)
  116. // through getUserMedia API.
  117. const base::Feature kAllowUserMediaAccess{"allow_user_media_access",
  118. base::FEATURE_DISABLED_BY_DEFAULT};
  119. // Enables the use of QUIC in Cast-specific NetworkContexts. See
  120. // chromecast/browser/cast_network_contexts.cc for usage.
  121. const base::Feature kEnableQuic{"enable_quic",
  122. base::FEATURE_DISABLED_BY_DEFAULT};
  123. // Enables triple-buffer 720p graphics (overriding default graphics buffer
  124. // settings for a platform).
  125. const base::Feature kTripleBuffer720{"enable_triple_buffer_720",
  126. base::FEATURE_DISABLED_BY_DEFAULT};
  127. // Enables single-buffered graphics (overriding default graphics buffer
  128. // settings and takes precedence over triple-buffer feature).
  129. const base::Feature kSingleBuffer{"enable_single_buffer",
  130. base::FEATURE_DISABLED_BY_DEFAULT};
  131. // Disable idle sockets closing on memory pressure. See
  132. // chromecast/browser/cast_network_contexts.cc for usage.
  133. const base::Feature kDisableIdleSocketsCloseOnMemoryPressure{
  134. "disable_idle_sockets_close_on_memory_pressure",
  135. base::FEATURE_DISABLED_BY_DEFAULT};
  136. const base::Feature kEnableGeneralAudienceBrowsing{
  137. "enable_general_audience_browsing", base::FEATURE_DISABLED_BY_DEFAULT};
  138. const base::Feature kEnableSideGesturePassThrough{
  139. "enable_side_gesture_pass_through", base::FEATURE_DISABLED_BY_DEFAULT};
  140. // Uses AudioManagerAndroid, instead of CastAudioManagerAndroid. This will
  141. // disable lots of Cast features, so it should only be used for development and
  142. // testing.
  143. const base::Feature kEnableChromeAudioManagerAndroid{
  144. "enable_chrome_audio_manager_android", base::FEATURE_DISABLED_BY_DEFAULT};
  145. // Enables CastAudioOutputDevice for audio output on Android. When disabled,
  146. // CastAudioManagerAndroid will be used.
  147. const base::Feature kEnableCastAudioOutputDevice{
  148. "enable_cast_audio_output_device", base::FEATURE_DISABLED_BY_DEFAULT};
  149. // End Chromecast Feature definitions.
  150. const base::Feature* kFeatures[] = {
  151. &kAllowUserMediaAccess,
  152. &kEnableQuic,
  153. &kTripleBuffer720,
  154. &kSingleBuffer,
  155. &kDisableIdleSocketsCloseOnMemoryPressure,
  156. &kEnableGeneralAudienceBrowsing,
  157. &kEnableSideGesturePassThrough,
  158. &kEnableChromeAudioManagerAndroid,
  159. &kEnableCastAudioOutputDevice,
  160. };
  161. std::vector<const base::Feature*> GetInternalFeatures();
  162. const std::vector<const base::Feature*>& GetFeatures() {
  163. static const base::NoDestructor<std::vector<const base::Feature*>> features(
  164. [] {
  165. std::vector<const base::Feature*> features(std::begin(kFeatures),
  166. std::end(kFeatures));
  167. auto internal_features = GetInternalFeatures();
  168. features.insert(features.end(), internal_features.begin(),
  169. internal_features.end());
  170. return features;
  171. }());
  172. if (GetTestFeatures().size() > 0)
  173. return GetTestFeatures();
  174. return *features;
  175. }
  176. void InitializeFeatureList(const base::Value::Dict& dcs_features,
  177. const base::Value::List& dcs_experiment_ids,
  178. const std::string& cmd_line_enable_features,
  179. const std::string& cmd_line_disable_features,
  180. const std::string& extra_enable_features,
  181. const std::string& extra_disable_features) {
  182. DCHECK(!base::FeatureList::GetInstance());
  183. // Set the experiments.
  184. SetExperimentIds(dcs_experiment_ids);
  185. std::string all_enable_features =
  186. cmd_line_enable_features + "," + extra_enable_features;
  187. std::string all_disable_features =
  188. cmd_line_disable_features + "," + extra_disable_features;
  189. // Initialize the FeatureList from the command line.
  190. auto feature_list = std::make_unique<base::FeatureList>();
  191. feature_list->InitializeFromCommandLine(all_enable_features,
  192. all_disable_features);
  193. // Override defaults from the DCS config.
  194. for (const auto kv : dcs_features) {
  195. // Each feature must have its own FieldTrial object. Since experiments are
  196. // controlled server-side for Chromecast, and this class is designed with a
  197. // client-side experimentation framework in mind, these parameters are
  198. // carefully chosen:
  199. // - The field trial name is unused for our purposes. However, we need to
  200. // maintain a 1:1 mapping with Features in order to properly store and
  201. // access parameters associated with each Feature. Therefore, use the
  202. // Feature's name as the FieldTrial name to ensure uniqueness.
  203. // - The probability is hard-coded to 100% so that the FeatureList always
  204. // respects the value from DCS.
  205. // - The default group is unused; it will be the same for every feature.
  206. // - SESSION_RANDOMIZED is used to prevent the need for an
  207. // entropy_provider. However, this value doesn't matter.
  208. // - We don't care about the group_id.
  209. //
  210. const std::string& feature_name = kv.first;
  211. auto* field_trial = base::FieldTrialList::FactoryGetFieldTrial(
  212. feature_name, k100PercentProbability, kDefaultDCSFeaturesGroup,
  213. base::FieldTrial::SESSION_RANDOMIZED, nullptr);
  214. if (kv.second.is_bool()) {
  215. // A boolean entry simply either enables or disables a feature.
  216. feature_list->RegisterFieldTrialOverride(
  217. feature_name,
  218. kv.second.GetBool() ? base::FeatureList::OVERRIDE_ENABLE_FEATURE
  219. : base::FeatureList::OVERRIDE_DISABLE_FEATURE,
  220. field_trial);
  221. continue;
  222. }
  223. if (kv.second.is_dict()) {
  224. // A dictionary entry implies that the feature is enabled.
  225. feature_list->RegisterFieldTrialOverride(
  226. feature_name, base::FeatureList::OVERRIDE_ENABLE_FEATURE,
  227. field_trial);
  228. // If the feature has not been overriden from the command line, set its
  229. // parameters accordingly.
  230. if (!feature_list->IsFeatureOverriddenFromCommandLine(
  231. feature_name, base::FeatureList::OVERRIDE_DISABLE_FEATURE)) {
  232. // Build a map of the FieldTrial parameters and associate it to the
  233. // FieldTrial.
  234. base::FieldTrialParams params;
  235. for (const auto params_kv : kv.second.DictItems()) {
  236. if (params_kv.second.is_string()) {
  237. params[params_kv.first] = params_kv.second.GetString();
  238. } else {
  239. LOG(ERROR) << "Entry in params dict for \"" << feature_name << "\""
  240. << " feature is not a string. Skipping.";
  241. }
  242. }
  243. // Register the params, so that they can be queried by client code.
  244. bool success = base::AssociateFieldTrialParams(
  245. feature_name, kDefaultDCSFeaturesGroup, params);
  246. DCHECK(success);
  247. }
  248. continue;
  249. }
  250. // Other base::Value types are not supported.
  251. LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
  252. << feature_name << " type: " << kv.second.type();
  253. }
  254. base::FeatureList::SetInstance(std::move(feature_list));
  255. }
  256. bool IsFeatureEnabled(const base::Feature& feature) {
  257. DCHECK(base::Contains(GetFeatures(), &feature)) << feature.name;
  258. return base::FeatureList::IsEnabled(feature);
  259. }
  260. base::Value::Dict GetOverriddenFeaturesForStorage(
  261. const base::Value::Dict& features) {
  262. base::Value::Dict persistent_dict;
  263. // |features| maps feature names to either a boolean or a dict of params.
  264. for (const auto feature : features) {
  265. if (feature.second.is_bool()) {
  266. persistent_dict.Set(feature.first, feature.second.GetBool());
  267. continue;
  268. }
  269. if (feature.second.is_dict()) {
  270. const base::Value* params_dict = &feature.second;
  271. base::Value::Dict params;
  272. for (const auto [param_key, param_val] : params_dict->GetDict()) {
  273. if (param_val.is_bool()) {
  274. params.Set(param_key, param_val.GetBool() ? "true" : "false");
  275. } else if (param_val.is_int()) {
  276. params.Set(param_key, base::NumberToString(param_val.GetInt()));
  277. } else if (param_val.is_double()) {
  278. params.Set(param_key, base::NumberToString(param_val.GetDouble()));
  279. } else if (param_val.is_string()) {
  280. params.Set(param_key, param_val.GetString());
  281. } else {
  282. LOG(ERROR) << "Entry in params dict for \"" << feature.first << "\""
  283. << " is not of a supported type (key: " << param_key
  284. << ", type: " << param_val.type();
  285. }
  286. }
  287. persistent_dict.Set(feature.first, std::move(params));
  288. continue;
  289. }
  290. // Other base::Value types are not supported.
  291. LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
  292. << feature.first << " type: " << feature.second.type();
  293. }
  294. return persistent_dict;
  295. }
  296. const std::unordered_set<int32_t>& GetDCSExperimentIds() {
  297. DCHECK(g_experiment_ids_initialized);
  298. return GetExperimentIds();
  299. }
  300. void ResetCastFeaturesForTesting() {
  301. g_experiment_ids_initialized = false;
  302. base::FeatureList::ClearInstanceForTesting();
  303. GetTestFeatures().clear();
  304. }
  305. void SetFeaturesForTest(std::vector<const base::Feature*> features) {
  306. GetTestFeatures() = std::move(features);
  307. }
  308. } // namespace chromecast