// 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.chrome.browser.app.flags;
import android.text.TextUtils;
import androidx.annotation.AnyThread;
import androidx.annotation.VisibleForTesting;
import org.jni_zero.CalledByNative;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.cached_flags.CachedFieldTrialParameter;
import org.chromium.base.cached_flags.CachedFlag;
import org.chromium.base.cached_flags.CachedFlagUtils;
import org.chromium.base.cached_flags.CachedFlagsSafeMode;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.build.BuildConfig;
import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
import org.chromium.chrome.browser.JankTrackerExperiment;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.customtabs.features.minimizedcustomtab.MinimizedFeatureUtils;
import org.chromium.chrome.browser.firstrun.FirstRunUtils;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.hub.HubFieldTrial;
import org.chromium.chrome.browser.logo.LogoUtils;
import org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.new_tab_url.DseNewTabUrlManager;
import org.chromium.chrome.browser.notifications.chime.ChimeFeatures;
import org.chromium.chrome.browser.omaha.VersionNumberGetter;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuidePushNotificationManager;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.searchwidget.SearchActivity;
import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegate;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabDataService;
import org.chromium.chrome.browser.tab_resumption.TabResumptionModuleUtils;
import org.chromium.chrome.browser.tabbed_mode.TabbedSystemUiCoordinator;
import org.chromium.chrome.browser.tabpersistence.TabStateFileManager;
import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.tasks.tab_management.TabManagementFieldTrial;
import org.chromium.chrome.browser.ui.google_bottom_bar.BottomBarConfigCreator;
import org.chromium.chrome.browser.webapps.WebappLauncherActivity;
import org.chromium.components.omnibox.OmniboxFeatures;
import org.chromium.components.signin.SigninFeatureMap;
import java.util.ArrayList;
import java.util.List;
/** Caches the flags that Chrome might require before native is loaded in a later next run. */
public class ChromeCachedFlags {
private static final ChromeCachedFlags INSTANCE = new ChromeCachedFlags();
private boolean mIsFinishedCachingNativeFlags;
/**
* A list of field trial parameters that will be cached when starting minimal browser mode. See
* {@link #cacheMinimalBrowserFlags()}.
*/
private static final List<CachedFieldTrialParameter> MINIMAL_BROWSER_FIELD_TRIALS =
List.of(
// This is used by CustomTabsConnection implementation, which does not
// necessarily start chrome.
CustomTabActivity.EXPERIMENTS_FOR_AGSA_PARAMS);
/**
* @return The {@link ChromeCachedFlags} singleton.
*/
public static ChromeCachedFlags getInstance() {
return INSTANCE;
}
/**
* Caches flags that are needed by Activities that launch before the native library is loaded
* and stores them in SharedPreferences. Because this function is called during launch after the
* library has loaded, they won't affect the next launch until Chrome is restarted.
*/
public void cacheNativeFlags() {
if (mIsFinishedCachingNativeFlags) return;
FirstRunUtils.cacheFirstRunPrefs();
CachedFlagUtils.cacheNativeFlags(
ChromeFeatureList.sFlagsCachedFullBrowser,
OmniboxFeatures.getFieldTrialsToCache(),
SigninFeatureMap.sCachedFlags);
cacheAdditionalNativeFlags();
List<CachedFieldTrialParameter> fieldTrialsToCache =
List.of(
BackPressManager.TAB_HISTORY_RECOVER,
ChimeFeatures.ALWAYS_REGISTER,
ChromeBaseAppCompatActivity.DEFAULT_FONT_FAMILY_TESTING,
TabbedSystemUiCoordinator.NAV_BAR_COLOR_ANIMATION_DISABLED_CACHED_PARAM,
CustomTabIntentDataProvider.AUTO_TRANSLATE_ALLOW_ALL_FIRST_PARTIES,
CustomTabIntentDataProvider.AUTO_TRANSLATE_PACKAGE_NAME_ALLOWLIST,
CustomTabIntentDataProvider.THIRD_PARTIES_DEFAULT_POLICY,
CustomTabIntentDataProvider.DENYLIST_ENTRIES,
CustomTabIntentDataProvider.ALLOWLIST_ENTRIES,
CustomTabIntentDataProvider.OMNIBOX_ALLOWED_PACKAGE_NAMES,
DseNewTabUrlManager.SWAP_OUT_NTP,
BottomBarConfigCreator.GOOGLE_BOTTOM_BAR_PARAM_BUTTON_LIST,
BottomBarConfigCreator.GOOGLE_BOTTOM_BAR_VARIANT_LAYOUT_VALUE,
BottomBarConfigCreator.GOOGLE_BOTTOM_BAR_NO_VARIANT_HEIGHT_DP_PARAM_VALUE,
BottomBarConfigCreator
.GOOGLE_BOTTOM_BAR_SINGLE_DECKER_HEIGHT_DP_PARAM_VALUE,
BottomBarConfigCreator.IS_GOOGLE_DEFAULT_SEARCH_ENGINE_CHECK_ENABLED,
HubFieldTrial.ALTERNATIVE_FAB_COLOR,
HubFieldTrial.PANE_SWITCHER_USES_TEXT,
HubFieldTrial.SUPPORTS_OTHER_TABS,
HubFieldTrial.SUPPORTS_SEARCH,
HubFieldTrial.SUPPORTS_BOOKMARKS,
JankTrackerExperiment.JANK_TRACKER_DELAYED_START_MS,
MinimizedFeatureUtils.ICON_VARIANT,
MinimizedFeatureUtils.MANUFACTURER_EXCLUDE_LIST,
MultiWindowUtils.BACK_TO_BACK_CTA_CREATION_TIMESTAMP_DIFF_THRESHOLD_MS,
OptimizationGuidePushNotificationManager.MAX_CACHE_SIZE,
SearchActivity.SEARCH_IN_CCT_APPLY_REFERRER_ID,
ShoppingPersistedTabDataService
.SKIP_SHOPPING_PERSISTED_TAB_DATA_DELAYED_INITIALIZATION,
ReturnToChromeUtil.HOME_SURFACE_RETURN_TIME_SECONDS,
LogoUtils.LOGO_POLISH_LARGE_SIZE,
LogoUtils.LOGO_POLISH_MEDIUM_SIZE,
SuggestionsNavigationDelegate.MOST_VISITED_TILES_RESELECT_LAX_PATH,
SuggestionsNavigationDelegate.MOST_VISITED_TILES_RESELECT_LAX_QUERY,
SuggestionsNavigationDelegate.MOST_VISITED_TILES_RESELECT_LAX_REF,
SuggestionsNavigationDelegate.MOST_VISITED_TILES_RESELECT_LAX_SCHEME_HOST,
TabManagementFieldTrial.DELAY_TEMP_STRIP_TIMEOUT_MS,
HomeModulesMetricsUtils.HOME_MODULES_SHOW_ALL_MODULES,
HomeModulesMetricsUtils.TAB_RESUMPTION_COMBINE_TABS,
TabGroupModelFilter.SHOW_TAB_GROUP_CREATION_DIALOG_SETTING,
TabGroupModelFilter.SKIP_TAB_GROUP_CREATION_DIALOG,
TabResumptionModuleUtils.TAB_RESUMPTION_DISABLE_BLEND,
TabResumptionModuleUtils.TAB_RESUMPTION_FETCH_HISTORY_BACKEND,
TabResumptionModuleUtils.TAB_RESUMPTION_FETCH_LOCAL_TABS_BACKEND,
TabResumptionModuleUtils.TAB_RESUMPTION_MAX_TILES_NUMBER,
TabResumptionModuleUtils.TAB_RESUMPTION_SHOW_DEFAULT_REASON,
TabResumptionModuleUtils.TAB_RESUMPTION_SHOW_SEE_MORE,
TabResumptionModuleUtils.TAB_RESUMPTION_USE_DEFAULT_APP_FILTER,
TabResumptionModuleUtils.TAB_RESUMPTION_USE_SALIENT_IMAGE,
TabResumptionModuleUtils.TAB_RESUMPTION_V2,
TabStateFileManager.MIGRATE_STALE_TABS_CACHED_PARAM,
VersionNumberGetter.MIN_SDK_VERSION,
WebappLauncherActivity.MIN_SHELL_APK_VERSION);
tryToCatchMissingParameters(
fieldTrialsToCache, OmniboxFeatures.getFieldTrialParamsToCache());
CachedFlagUtils.cacheFieldTrialParameters(
fieldTrialsToCache, OmniboxFeatures.getFieldTrialParamsToCache());
CachedFlagsSafeMode.getInstance().onEndCheckpoint();
mIsFinishedCachingNativeFlags = true;
}
private void tryToCatchMissingParameters(
List<CachedFieldTrialParameter>... listsOfParamsToTest) {
if (!BuildConfig.ENABLE_ASSERTS) return;
var paramsToTest = new ArrayList<CachedFieldTrialParameter>();
for (List<CachedFieldTrialParameter> list : listsOfParamsToTest) {
paramsToTest.addAll(list);
}
// All instances of CachedFieldTrialParameter should be manually passed to
// CachedFeatureFlags.cacheFieldTrialParameters(). The following checking is a best-effort
// attempt to try to catch accidental omissions. It cannot replace the list because some
// instances might not be instantiated if the classes they belong to are not accessed yet.
List<String> omissions = new ArrayList<>();
for (CachedFieldTrialParameter trial : CachedFieldTrialParameter.getAllInstances()) {
if (paramsToTest.contains(trial)) continue;
if (MINIMAL_BROWSER_FIELD_TRIALS.contains(trial)) continue;
omissions.add(trial.getFeatureName() + ":" + trial.getParameterName());
}
assert omissions.isEmpty()
: "The following trials are not correctly cached: "
+ TextUtils.join(", ", omissions);
}
/**
* Caches flags that are enabled in minimal browser mode and must take effect on startup but are
* set via native code. This function needs to be called in minimal browser mode to mark these
* field trials as active, otherwise histogram data recorded in minimal browser mode won't be
* tagged with their corresponding field trial experiments.
*/
public void cacheMinimalBrowserFlags() {
cacheMinimalBrowserFlagsTimeFromNativeTime();
CachedFlagUtils.cacheNativeFlags(ChromeFeatureList.sFlagsCachedInMinimalBrowser);
CachedFlagUtils.cacheFieldTrialParameters(MINIMAL_BROWSER_FIELD_TRIALS);
}
/**
* Caches a predetermined list of flags that must take effect on startup but are set via native
* code.
*
* Do not add new simple boolean flags here, add them to {@link #cacheNativeFlags} instead.
*/
public static void cacheAdditionalNativeFlags() {
// Propagate the BACKGROUND_THREAD_POOL feature value to LibraryLoader.
LibraryLoader.setBackgroundThreadPoolEnabledOnNextRuns(
ChromeFeatureList.isEnabled(ChromeFeatureList.BACKGROUND_THREAD_POOL));
// Propagate the CACHE_ACTIVITY_TASKID feature value to ApplicationStatus.
ApplicationStatus.setCachingEnabled(
ChromeFeatureList.isEnabled(ChromeFeatureList.CACHE_ACTIVITY_TASKID));
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static void cacheMinimalBrowserFlagsTimeFromNativeTime() {
ChromeSharedPreferences.getInstance()
.writeLong(
ChromePreferenceKeys.FLAGS_LAST_CACHED_MINIMAL_BROWSER_FLAGS_TIME_MILLIS,
System.currentTimeMillis());
}
public static long getLastCachedMinimalBrowserFlagsTimeMillis() {
return ChromeSharedPreferences.getInstance()
.readLong(
ChromePreferenceKeys.FLAGS_LAST_CACHED_MINIMAL_BROWSER_FLAGS_TIME_MILLIS,
0);
}
@CalledByNative
@AnyThread
static boolean isEnabled(String featureName) {
CachedFlag cachedFlag = ChromeFeatureList.sAllCachedFlags.get(featureName);
assert cachedFlag != null;
return cachedFlag.isEnabled();
}
}