chromium/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabFeatureOverridesManager.java

// Copyright 2024 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.customtabs;

import android.content.Intent;

import org.chromium.base.CommandLine;
import org.chromium.base.IntentUtils;
import org.chromium.base.Log;
import org.chromium.base.ResettersForTesting;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.flags.ChromeFeatureList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;

/**
 * Class that allows the Custom Tab client to override features for a single session. Note that this
 * class is meant to be used for experimentation purposes only, and the features will be removed
 * from the `ALLOWED_FEATURES` list once they fully ship to Stable.
 */
@ActivityScope
public class CustomTabFeatureOverridesManager {
    private static final String TAG = "CTFeatureOvrdMgr";
    private static final Set<String> ALLOWED_FEATURES =
            new HashSet<>(Arrays.asList(ChromeFeatureList.CCT_MINIMIZED));

    private static Set<String> sAllowedFeaturesForTesting;

    private Map<String, Boolean> mFeatureOverrides;

    @Inject
    CustomTabFeatureOverridesManager(BrowserServicesIntentDataProvider intentDataProvider) {
        if (ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled()
                && (CommandLine.getInstance().hasSwitch("cct-client-firstparty-override")
                        || intentDataProvider.isTrustedIntent())) {
            setUpFeatureOverrides(
                    intentDataProvider.getIntent(),
                    sAllowedFeaturesForTesting != null
                            ? sAllowedFeaturesForTesting
                            : ALLOWED_FEATURES);
        }
    }

    /**
     * @param feature The feature to check for an override value.
     * @return Whether the feature is overridden and enabled, null if it's not overridden or if
     *     overrides aren't allowed.
     */
    public Boolean isFeatureEnabled(String feature) {
        if (mFeatureOverrides == null || mFeatureOverrides.isEmpty()) return null;
        return mFeatureOverrides.get(feature);
    }

    private void setUpFeatureOverrides(Intent intent, Set<String> allowedFeatures) {
        mFeatureOverrides = new HashMap<>();
        ArrayList<String> enabledFeatures =
                IntentUtils.safeGetStringArrayListExtra(
                        intent, CustomTabIntentDataProvider.EXPERIMENTS_ENABLE);
        ArrayList<String> disabledFeatures =
                IntentUtils.safeGetStringArrayListExtra(
                        intent, CustomTabIntentDataProvider.EXPERIMENTS_DISABLE);
        if (enabledFeatures != null) {
            for (var feature : enabledFeatures) {
                if (!allowedFeatures.contains(feature)) {
                    Log.e(TAG, "The feature " + feature + " is not allowed to be overridden.");
                    continue;
                }
                mFeatureOverrides.put(feature, true);
            }
        }
        if (disabledFeatures != null) {
            for (var feature : disabledFeatures) {
                if (!allowedFeatures.contains(feature)) {
                    Log.e(TAG, "The feature " + feature + " is not allowed to be overridden.");
                    continue;
                }
                if (mFeatureOverrides.containsKey(feature)) {
                    mFeatureOverrides.put(feature, null);
                    Log.e(TAG, "There are conflicting override values for the feature " + feature);
                    continue;
                }
                mFeatureOverrides.put(feature, false);
            }
        }
    }

    public static void setAllowedFeaturesForTesting(Set<String> allowedFeatures) {
        sAllowedFeaturesForTesting = allowedFeatures;
        ResettersForTesting.register(() -> sAllowedFeaturesForTesting = null);
    }
}