blob: b2a45b0f1e376e14d6a7baee82ef69807a234471 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.build.annotations.MainDex;
import java.util.HashMap;
import java.util.Map;
/**
* Provides shared capabilities for feature flag support.
*/
@JNINamespace("base::android")
@MainDex
public class FeatureList {
/**
* Test value overrides for tests without native.
*/
public static class TestValues {
private Map<String, Boolean> mFeatureFlags = new HashMap<>();
private Map<String, String> mFieldTrialParams = new HashMap<>();
/**
* Constructor.
*/
public TestValues() {}
/**
* Set overrides for feature flags.
*/
public void setFeatureFlagsOverride(Map<String, Boolean> featureFlags) {
mFeatureFlags = featureFlags;
}
/**
* Add an override for a feature flag.
*/
public void addFeatureFlagOverride(String featureName, boolean testValue) {
mFeatureFlags.put(featureName, testValue);
}
/**
* Add an override for a field trial parameter.
*/
public void addFieldTrialParamOverride(
String featureName, String paramName, String testValue) {
mFieldTrialParams.put(makeKey(featureName, paramName), testValue);
}
Boolean getFeatureFlagOverride(String featureName) {
return mFeatureFlags.get(featureName);
}
String getFieldTrialParamOverride(String featureName, String paramName) {
return mFieldTrialParams.get(makeKey(featureName, paramName));
}
private static String makeKey(String featureName, String paramName) {
return featureName + ":" + paramName;
}
}
/** Map that stores substitution feature flags for tests. */
private static @Nullable TestValues sTestFeatures;
/** Access to default values of the native feature flag. */
private static boolean sTestCanUseDefaults;
private FeatureList() {}
/**
* @return Whether the native FeatureList has been initialized. If this method returns false,
* none of the methods in this class that require native access should be called (except
* in tests if test features have been set).
*/
public static boolean isInitialized() {
return hasTestFeatures() || isNativeInitialized();
}
/**
* @return Whether the native FeatureList is initialized or not.
*/
public static boolean isNativeInitialized() {
if (!LibraryLoader.getInstance().isInitialized()) return false;
// Even if the native library is loaded, the C++ FeatureList might not be initialized yet.
// In that case, accessing it will not immediately fail, but instead cause a crash later
// when it is initialized. Return whether the native FeatureList has been initialized,
// so the return value can be tested, or asserted for a more actionable stack trace
// on failure.
//
// The FeatureList is however guaranteed to be initialized by the time
// AsyncInitializationActivity#finishNativeInitialization is called.
return FeatureListJni.get().isInitialized();
}
/**
* This is called explicitly for instrumentation tests via Features#applyForInstrumentation().
* Unit tests and Robolectric tests must not invoke this and should rely on the {@link Features}
* annotations to enable or disable any feature flags.
*/
@VisibleForTesting
public static void setTestCanUseDefaultsForTesting() {
sTestCanUseDefaults = true;
}
/**
* We reset the value to false after the instrumentation test to avoid any unwanted
* persistence of the state. This is invoked by Features#reset().
*/
@VisibleForTesting
public static void resetTestCanUseDefaultsForTesting() {
sTestCanUseDefaults = false;
}
/**
* Sets the feature flags to use in JUnit tests, since native calls are not available there.
*/
@VisibleForTesting
public static void setTestFeatures(Map<String, Boolean> testFeatures) {
if (testFeatures == null) {
setTestValues(null);
} else {
TestValues testValues = new TestValues();
testValues.setFeatureFlagsOverride(testFeatures);
setTestValues(testValues);
}
}
/**
* Sets the feature flags and field trial parameters to use in JUnit tests, since native calls
* are not available there.
*/
@VisibleForTesting
public static void setTestValues(TestValues testFeatures) {
sTestFeatures = testFeatures;
}
/**
* @return Whether test feature values have been configured.
*/
public static boolean hasTestFeatures() {
return sTestFeatures != null;
}
/**
* @param featureName The name of the feature to query.
* @return Whether the feature has a test value configured.
*/
public static boolean hasTestFeature(String featureName) {
return hasTestFeatures() && sTestFeatures.mFeatureFlags.containsKey(featureName);
}
/**
* Returns the test value of the feature with the given name.
*
* @param featureName The name of the feature to query.
* @return The test value set for the feature, or null if no test value has been set.
* @throws IllegalArgumentException if no test value was set and default values aren't allowed.
*/
public static Boolean getTestValueForFeature(String featureName) {
if (hasTestFeatures()) {
Boolean override = sTestFeatures.getFeatureFlagOverride(featureName);
if (override != null) {
return override;
}
if (!sTestCanUseDefaults) {
throw new IllegalArgumentException("No test value configured for " + featureName
+ " and native is not available to provide a default value. Use"
+ " @EnableFeatures or @DisableFeatures to provide test values for the"
+ " flag.");
}
}
return null;
}
/**
* Returns the test value of the field trial parameter.
*
* @param featureName The name of the feature to query.
* @param paramName The name of the field trial parameter to query.
* @return The test value set for the parameter, or null if no test value has been set.
* @throws IllegalArgumentException if no test value was set and default values aren't allowed.
*/
public static String getTestValueForFieldTrialParam(String featureName, String paramName) {
if (hasTestFeatures()) {
String override = sTestFeatures.getFieldTrialParamOverride(featureName, paramName);
if (override != null) {
return override;
}
}
return null;
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@NativeMethods
public interface Natives {
boolean isInitialized();
}
}