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

// Copyright 2019 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 static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CustomTabProfileType.INCOGNITO;

import android.content.Intent;
import android.graphics.Rect;
import android.text.TextUtils;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;

import org.chromium.base.Callback;
import org.chromium.base.IntentUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneShotCallback;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.supplier.SupplierUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.app.reengagement.ReengagementActivity;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.TabBookmarker;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
import org.chromium.chrome.browser.crash.ChromePureJavaExceptionReporter;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabController;
import org.chromium.chrome.browser.customtabs.features.branding.BrandingController;
import org.chromium.chrome.browser.customtabs.features.minimizedcustomtab.CustomTabMinimizeDelegate;
import org.chromium.chrome.browser.customtabs.features.minimizedcustomtab.MinimizedFeatureUtils;
import org.chromium.chrome.browser.customtabs.features.partialcustomtab.CustomTabHeightStrategy;
import org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabDisplayManager;
import org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTabObserver;
import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabHistoryIPHController;
import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbar;
import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbarCoordinator;
import org.chromium.chrome.browser.desktop_site.DesktopSiteSettingsIPHController;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.history.HistoryManager;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthCoordinatorFactory;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.page_info.ChromePageInfo;
import org.chromium.chrome.browser.page_info.ChromePageInfoHighlight;
import org.chromium.chrome.browser.privacy_sandbox.ActivityTypeMapper;
import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridge;
import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxDialogController;
import org.chromium.chrome.browser.privacy_sandbox.SurfaceType;
import org.chromium.chrome.browser.privacy_sandbox.TrackingProtectionSnackbarController;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.readaloud.ReadAloudIPHController;
import org.chromium.chrome.browser.reengagement.ReengagementNotificationController;
import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.RequestDesktopUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.ui.RootUiCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuBlocker;
import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeController;
import org.chromium.chrome.browser.ui.google_bottom_bar.GoogleBottomBarCoordinator;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.ui.system.StatusBarColorController.StatusBarColorProvider;
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.page_info.PageInfoController.OpenedFromSource;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.ActivityWindowAndroid;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.IntentRequestTracker;
import org.chromium.ui.modaldialog.ModalDialogManager;

import java.util.function.BooleanSupplier;

/** A {@link RootUiCoordinator} variant that controls UI for {@link BaseCustomTabActivity}. */
public class BaseCustomTabRootUiCoordinator extends RootUiCoordinator {

    private final Supplier<CustomTabToolbarCoordinator> mToolbarCoordinator;
    private final Supplier<CustomTabActivityNavigationController> mNavigationController;
    private final Supplier<BrowserServicesIntentDataProvider> mIntentDataProvider;
    private final Supplier<CustomTabActivityTabController> mTabController;
    private final Supplier<CustomTabMinimizeDelegate> mMinimizeDelegateSupplier;
    private final Supplier<CustomTabFeatureOverridesManager> mFeatureOverridesManagerSupplier;

    private CustomTabHeightStrategy mCustomTabHeightStrategy;

    private @Nullable BrandingController mBrandingController;

    private @Nullable DesktopSiteSettingsIPHController mDesktopSiteSettingsIPHController;
    private @Nullable CustomTabHistoryIPHController mCustomTabHistoryIPHController;
    private @Nullable ReadAloudIPHController mReadAloudIPHController;
    private @Nullable GoogleBottomBarCoordinator mGoogleBottomBarCoordinator;
    private @Nullable TrackingProtectionSnackbarController mTrackingProtectionSnackbarController;

    /**
     * Construct a new BaseCustomTabRootUiCoordinator.
     *
     * @param activity The activity whose UI the coordinator is responsible for.
     * @param shareDelegateSupplier Supplies the {@link ShareDelegate}.
     * @param tabProvider The {@link ActivityTabProvider} to get current tab of the activity.
     * @param profileSupplier Supplier of the currently applicable profile.
     * @param bookmarkModelSupplier Supplier of the bookmark bridge for the current profile.
     * @param tabBookmarkerSupplier Supplier of {@link TabBookmarker} for bookmarking a given tab.
     * @param tabModelSelectorSupplier Supplies the {@link TabModelSelector}.
     * @param lastUserInteractionTimeSupplier Supplier of the last user interaction time.
     * @param browserControlsManager Manages the browser controls.
     * @param windowAndroid The current {@link WindowAndroid}.
     * @param activityLifecycleDispatcher Allows observation of the activity lifecycle.
     * @param layoutManagerSupplier Supplies the {@link LayoutManager}.
     * @param menuOrKeyboardActionController Controls the menu or keyboard action controller.
     * @param activityThemeColorSupplier Supplies the activity color theme.
     * @param modalDialogManagerSupplier Supplies the {@link ModalDialogManager}.
     * @param appMenuBlocker Controls the app menu blocking.
     * @param supportsAppMenuSupplier Supplies the support state for the app menu.
     * @param supportsFindInPage Supplies the support state for find in page.
     * @param tabCreatorManagerSupplier Supplies the {@link TabCreatorManager}.
     * @param fullscreenManager Manages the fullscreen state.
     * @param compositorViewHolderSupplier Supplies the {@link CompositorViewHolder}.
     * @param tabContentManagerSupplier Supplies the {@link TabContentManager}.
     * @param snackbarManagerSupplier Supplies the {@link SnackbarManager}.
     * @param activityType The {@link ActivityType} for the activity.
     * @param isInOverviewModeSupplier Supplies whether the app is in overview mode.
     * @param appMenuDelegate The app menu delegate.
     * @param statusBarColorProvider Provides the status bar color.
     * @param intentRequestTracker Tracks intent requests.
     * @param customTabToolbarCoordinator Coordinates the custom tab toolbar.
     * @param customTabNavigationController Controls the custom tab navigation.
     * @param intentDataProvider Contains intent information used to start the Activity.
     * @param tabController Activity tab controller.
     * @param minimizeDelegateSupplier Supplies the {@link CustomTabMinimizeDelegate} used to
     *     minimize the tab.
     * @param featureOverridesManagerSupplier Supplies the {@link CustomTabFeatureOverridesManager}.
     * @param baseChromeLayout The base view hosting Chrome that certain views (e.g. the omnibox
     *     suggestion list) will position themselves relative to. If null, the content view will be
     *     used.
     */
    public BaseCustomTabRootUiCoordinator(
            @NonNull AppCompatActivity activity,
            @NonNull ObservableSupplier<ShareDelegate> shareDelegateSupplier,
            @NonNull ActivityTabProvider tabProvider,
            @NonNull ObservableSupplier<Profile> profileSupplier,
            @NonNull ObservableSupplier<BookmarkModel> bookmarkModelSupplier,
            @NonNull ObservableSupplier<TabBookmarker> tabBookmarkerSupplier,
            @NonNull ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
            @NonNull Supplier<Long> lastUserInteractionTimeSupplier,
            @NonNull BrowserControlsManager browserControlsManager,
            @NonNull ActivityWindowAndroid windowAndroid,
            @NonNull ActivityLifecycleDispatcher activityLifecycleDispatcher,
            @NonNull ObservableSupplier<LayoutManagerImpl> layoutManagerSupplier,
            @NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
            @NonNull Supplier<Integer> activityThemeColorSupplier,
            @NonNull ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier,
            @NonNull AppMenuBlocker appMenuBlocker,
            @NonNull BooleanSupplier supportsAppMenuSupplier,
            @NonNull BooleanSupplier supportsFindInPage,
            @NonNull Supplier<TabCreatorManager> tabCreatorManagerSupplier,
            @NonNull FullscreenManager fullscreenManager,
            @NonNull ObservableSupplier<CompositorViewHolder> compositorViewHolderSupplier,
            @NonNull Supplier<TabContentManager> tabContentManagerSupplier,
            @NonNull Supplier<SnackbarManager> snackbarManagerSupplier,
            @NonNull ObservableSupplierImpl<EdgeToEdgeController> edgeToEdgeControllerSupplier,
            @ActivityType int activityType,
            @NonNull Supplier<Boolean> isInOverviewModeSupplier,
            @NonNull AppMenuDelegate appMenuDelegate,
            @NonNull StatusBarColorProvider statusBarColorProvider,
            @NonNull IntentRequestTracker intentRequestTracker,
            @NonNull Supplier<CustomTabToolbarCoordinator> customTabToolbarCoordinator,
            @NonNull Supplier<CustomTabActivityNavigationController> customTabNavigationController,
            @NonNull Supplier<BrowserServicesIntentDataProvider> intentDataProvider,
            @NonNull BackPressManager backPressManager,
            @NonNull Supplier<CustomTabActivityTabController> tabController,
            @NonNull Supplier<CustomTabMinimizeDelegate> minimizeDelegateSupplier,
            @NonNull Supplier<CustomTabFeatureOverridesManager> featureOverridesManagerSupplier,
            @Nullable View baseChromeLayout) {
        super(
                activity,
                null,
                shareDelegateSupplier,
                tabProvider,
                profileSupplier,
                bookmarkModelSupplier,
                tabBookmarkerSupplier,
                tabModelSelectorSupplier,
                new OneshotSupplierImpl<>(),
                new OneshotSupplierImpl<>(),
                new OneshotSupplierImpl<>(),
                new OneshotSupplierImpl<>(),
                lastUserInteractionTimeSupplier,
                browserControlsManager,
                windowAndroid,
                activityLifecycleDispatcher,
                layoutManagerSupplier,
                menuOrKeyboardActionController,
                activityThemeColorSupplier,
                modalDialogManagerSupplier,
                appMenuBlocker,
                supportsAppMenuSupplier,
                supportsFindInPage,
                tabCreatorManagerSupplier,
                fullscreenManager,
                compositorViewHolderSupplier,
                tabContentManagerSupplier,
                snackbarManagerSupplier,
                edgeToEdgeControllerSupplier,
                activityType,
                isInOverviewModeSupplier,
                appMenuDelegate,
                statusBarColorProvider,
                intentRequestTracker,
                new OneshotSupplierImpl<>(),
                false,
                backPressManager,
                null,
                /* overviewColorSupplier= */ null,
                baseChromeLayout);
        mToolbarCoordinator = customTabToolbarCoordinator;
        mNavigationController = customTabNavigationController;
        mIntentDataProvider = intentDataProvider;
        if (intentDataProvider.get().getActivityType() == ActivityType.CUSTOM_TAB
                && !intentDataProvider.get().isOpenedByChrome()
                && intentDataProvider.get().getCustomTabMode() != INCOGNITO) {
            String appId = mIntentDataProvider.get().getClientPackageName();
            if (TextUtils.isEmpty(appId)) {
                appId = CustomTabIntentDataProvider.getAppIdFromReferrer(activity);
            }
            String browserName = activity.getResources().getString(R.string.app_name);
            mBrandingController =
                    new BrandingController(
                            activity, appId, browserName, new ChromePureJavaExceptionReporter());
        }
        // TODO(353517557): Do initialization necessary for ActivityType.AUTH_TAB

        mTabController = tabController;
        mMinimizeDelegateSupplier = minimizeDelegateSupplier;
        mFeatureOverridesManagerSupplier = featureOverridesManagerSupplier;
        // TODO(crbug.com/41481778): move this RootUiCoordinator once this flag is removed.
        if (ChromeFeatureList.sCctTabModalDialog.isEnabled()) {
            getAppBrowserControlsVisibilityDelegate()
                    .addDelegate(browserControlsManager.getBrowserVisibilityDelegate());
        }
    }

    @Override
    protected void initializeToolbar() {
        super.initializeToolbar();

        mToolbarCoordinator.get().onToolbarInitialized(mToolbarManager);
        mNavigationController.get().onToolbarInitialized(mToolbarManager);

        CustomTabToolbar toolbar = mActivity.findViewById(R.id.toolbar);
        if (ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled()) {
            toolbar.setFeatureOverridesManager(mFeatureOverridesManagerSupplier.get());
        }
        View coordinator = mActivity.findViewById(R.id.coordinator);
        mCustomTabHeightStrategy.onToolbarInitialized(
                coordinator, toolbar, mIntentDataProvider.get().getPartialTabToolbarCornerRadius());
        if (mBrandingController != null) {
            mBrandingController.onToolbarInitialized(toolbar.getBrandingDelegate());
        }
        toolbar.setCloseButtonPosition(mIntentDataProvider.get().getCloseButtonPosition());
        if (mMinimizeDelegateSupplier.hasValue()) {
            toolbar.setMinimizeDelegate(mMinimizeDelegateSupplier.get());
        }
        if (!MinimizedFeatureUtils.shouldEnableMinimizedCustomTabs(mIntentDataProvider.get())) {
            toolbar.setMinimizeButtonEnabled(false);
        }
        if (mIntentDataProvider.get().isPartialCustomTab()) {
            Callback<Runnable> softInputCallback =
                    ((PartialCustomTabDisplayManager) mCustomTabHeightStrategy)::onShowSoftInput;

            var tabController = mTabController.get();
            tabController.registerTabObserver(new PartialCustomTabTabObserver(softInputCallback));
            var csManager = mContextualSearchManagerSupplier.get();
            if (csManager != null) {
                tabController.registerTabObserver(
                        new EmptyTabObserver() {
                            @Override
                            public void didFirstVisuallyNonEmptyPaint(Tab tab) {
                                csManager.setCanHideAndroidBrowserControls(false);
                            }
                        });
            }
        }

        CustomTabsConnection connection = CustomTabsConnection.getInstance();
        boolean shouldEnableOmnibox =
                connection.shouldEnableOmniboxForIntent(mIntentDataProvider.get());
        RecordHistogram.recordBooleanHistogram(
                "CustomTabs.Omnibox.EnabledState", shouldEnableOmnibox);
        if (shouldEnableOmnibox) {
            toolbar.setOmniboxEnabled(
                    mIntentDataProvider.get().getClientPackageName(),
                    connection.getAlternateOmniboxTapHandler(mIntentDataProvider.get()));
        }
    }

    @Override
    protected boolean canPreviewPromoteToTab() {
        return mActivityType == ActivityType.CUSTOM_TAB;
    }

    @Override
    public void onFinishNativeInitialization() {
        super.onFinishNativeInitialization();

        mGoogleBottomBarCoordinator = getGoogleBottomBarCoordinator();
        if (mGoogleBottomBarCoordinator != null) {
            mGoogleBottomBarCoordinator.onFinishNativeInitialization();
        }
        new OneShotCallback<>(
                mProfileSupplier,
                mCallbackController.makeCancelable(
                        profile -> {
                            assert profile != null : "Unexpectedly null profile from TabModel.";
                            if (profile == null) return;
                            if (ReengagementNotificationController.isEnabled()) {
                                Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
                                ReengagementNotificationController controller =
                                        new ReengagementNotificationController(
                                                mActivity, tracker, ReengagementActivity.class);
                                controller.tryToReengageTheUser();
                            }
                            if (mAppMenuCoordinator != null) {
                                mReadAloudIPHController =
                                        new ReadAloudIPHController(
                                                mActivity,
                                                profile,
                                                getToolbarManager().getMenuButtonView(),
                                                mAppMenuCoordinator.getAppMenuHandler(),
                                                mActivityTabProvider,
                                                mReadAloudControllerSupplier,
                                                /* showAppMenuTextBubble= */ false);
                            }
                        }));

        SupplierUtils.waitForAll(
                () -> initializeTrackingProtectionSnackbarController(),
                mActivityTabProvider,
                mProfileSupplier);
    }

    @Override
    protected void initProfileDependentFeatures(Profile currentlySelectedProfile) {
        super.initProfileDependentFeatures(currentlySelectedProfile);

        GoogleBottomBarCoordinator googleBottomBarCoordinator = getGoogleBottomBarCoordinator();

        if (googleBottomBarCoordinator != null) {
            googleBottomBarCoordinator.initDefaultSearchEngine(
                    currentlySelectedProfile.getOriginalProfile());
        }
    }

    private void initializeTrackingProtectionSnackbarController() {
        if (ChromeFeatureList.isEnabled(
                        ChromeFeatureList.TRACKING_PROTECTION_USER_BYPASS_PWA_TRIGGER)
                && mActivityType == ActivityType.WEB_APK) {

            mTrackingProtectionSnackbarController =
                    new TrackingProtectionSnackbarController(
                            getPageInfoSnackbarOnAction(),
                            mSnackbarManagerSupplier,
                            mActivityTabProvider.get().getWebContents(),
                            mProfileSupplier.get(),
                            mActivityType);
        }
    }

    public CustomTabHistoryIPHController getHistoryIPHController() {
        return mCustomTabHistoryIPHController;
    }

    @Override
    public int getControlContainerHeightResource() {
        return R.dimen.custom_tabs_control_container_height;
    }

    @Override
    protected boolean isContextualSearchEnabled() {
        if (mIntentDataProvider.get().isAuthTab()) return false;
        return super.isContextualSearchEnabled();
    }

    @Override
    public void createContextualSearchTab(String searchUrl) {
        if (mActivityTabProvider.get() == null) return;
        mActivityTabProvider.get().loadUrl(new LoadUrlParams(searchUrl));
    }

    // Google Bottom bar
    private @Nullable GoogleBottomBarCoordinator maybeCreateGoogleBottomBarComponents() {
        if (!isGoogleBottomBarEnabled()) {
            return null;
        }

        return new GoogleBottomBarCoordinator(
                mActivity,
                mActivityTabProvider,
                mShareDelegateSupplier,
                CustomTabsConnection.getInstance()
                        .getGoogleBottomBarIntentParams(mIntentDataProvider.get()),
                mIntentDataProvider.get().getCustomButtonsOnGoogleBottomBar());
    }

    public @Nullable GoogleBottomBarCoordinator getGoogleBottomBarCoordinator() {
        if (mGoogleBottomBarCoordinator == null) {
            mGoogleBottomBarCoordinator = maybeCreateGoogleBottomBarComponents();
        }
        return mGoogleBottomBarCoordinator;
    }

    private boolean isGoogleBottomBarEnabled() {
        return isGoogleBottomBarEnabled(mIntentDataProvider.get());
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    static boolean isGoogleBottomBarEnabled(BrowserServicesIntentDataProvider intentDataProvider) {
        return GoogleBottomBarCoordinator.isFeatureEnabled()
                && CustomTabsConnection.getInstance()
                        .shouldEnableGoogleBottomBarForIntent(intentDataProvider);
    }

    @Override
    protected IncognitoReauthCoordinatorFactory getIncognitoReauthCoordinatorFactory(
            Profile profile) {
        // TODO(crbug.com/335609494): Disable this for ephemeral CCTs.
        Intent showRegularOverviewIntent = new Intent(Intent.ACTION_MAIN);
        showRegularOverviewIntent.setClass(mActivity, ChromeLauncherActivity.class);
        showRegularOverviewIntent.putExtra(IntentHandler.EXTRA_OPEN_REGULAR_OVERVIEW_MODE, true);
        IntentUtils.addTrustedIntentExtras(showRegularOverviewIntent);

        return new IncognitoReauthCoordinatorFactory(
                mActivity,
                mTabModelSelectorSupplier.get(),
                mModalDialogManagerSupplier.get(),
                new IncognitoReauthManager(mActivity, profile),
                /* layoutManager= */ null,
                /* hubManagerSupplier= */ null,
                /* showRegularOverviewIntent= */ showRegularOverviewIntent,
                /* isTabbedActivity= */ false);
    }

    @Override
    protected boolean shouldAllowThemingInNightMode() {
        return mActivityType == ActivityType.TRUSTED_WEB_ACTIVITY
                || mActivityType == ActivityType.WEB_APK;
    }

    @Override
    protected boolean shouldAllowBrightThemeColors() {
        return mActivityType == ActivityType.TRUSTED_WEB_ACTIVITY
                || mActivityType == ActivityType.WEB_APK;
    }

    @Override
    public void onPreInflationStartup() {
        super.onPreInflationStartup();

        BrowserServicesIntentDataProvider intentDataProvider = mIntentDataProvider.get();
        assert intentDataProvider != null
                : "IntentDataProvider needs to be non-null after preInflationStartup";

        mCustomTabHeightStrategy =
                CustomTabHeightStrategy.createStrategy(
                        mActivity,
                        intentDataProvider,
                        () -> mCompositorViewHolderSupplier.get(),
                        () -> mTabModelSelectorSupplier.get().getCurrentTab(),
                        CustomTabsConnection.getInstance(),
                        mActivityLifecycleDispatcher,
                        mFullscreenManager,
                        DeviceFormFactor.isWindowOnTablet(mWindowAndroid));
    }

    @Override
    public void onPostInflationStartup() {
        super.onPostInflationStartup();
        mCustomTabHeightStrategy.onPostInflationStartup();
        if (HistoryManager.isAppSpecificHistoryEnabled() && mAppMenuCoordinator != null) {
            mCustomTabHistoryIPHController =
                    new CustomTabHistoryIPHController(
                            mActivity,
                            mActivityTabProvider,
                            mProfileSupplier,
                            mAppMenuCoordinator.getAppMenuHandler());
        }
    }

    @Override
    protected boolean showWebSearchInActionMode() {
        return !mIntentDataProvider.get().isAuthTab();
    }

    @Override
    protected void setStatusBarScrimFraction(float scrimFraction) {
        super.setStatusBarScrimFraction(scrimFraction);
        // TODO(jinsukkim): Separate CCT scrim update action from status bar scrim stuff.
        mCustomTabHeightStrategy.setScrimFraction(scrimFraction);
    }

    @Override
    protected Rect getAppRectOnScreen() {
        // This is necessary if app handler cannot rely on the popup window that ensures the menu
        // will not be clipped off the screen, which can happen in partial CCT.
        // TODO(crbug.com/40877078): Add a render test to prevent regressions.
        if (mIntentDataProvider.get().isPartialCustomTab()) {
            View coord = mActivity.findViewById(R.id.coordinator);
            int[] location = new int[2];
            coord.getLocationOnScreen(location);
            return new Rect(
                    location[0],
                    location[1],
                    location[0] + coord.getWidth(),
                    location[1] + coord.getHeight());
        }
        return super.getAppRectOnScreen();
    }

    @Override
    protected void onFindToolbarShown() {
        super.onFindToolbarShown();
        CustomTabToolbar toolbar = mActivity.findViewById(R.id.toolbar);
        toolbar.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
        mCustomTabHeightStrategy.onFindToolbarShown();
    }

    @Override
    protected void onFindToolbarHidden() {
        super.onFindToolbarHidden();
        CustomTabToolbar toolbar = mActivity.findViewById(R.id.toolbar);
        toolbar.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
        mCustomTabHeightStrategy.onFindToolbarHidden();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (mBrandingController != null) {
            mBrandingController.destroy();
            mBrandingController = null;
        }

        if (mDesktopSiteSettingsIPHController != null) {
            mDesktopSiteSettingsIPHController.destroy();
            mDesktopSiteSettingsIPHController = null;
        }

        if (mReadAloudIPHController != null) {
            mReadAloudIPHController.destroy();
            mReadAloudIPHController = null;
        }

        mCustomTabHeightStrategy.destroy();

        if (mCustomTabHistoryIPHController != null) {
            mCustomTabHistoryIPHController.destroy();
            mCustomTabHistoryIPHController = null;
        }
    }

    /**
     * Delegates changing the background color to the {@link CustomTabHeightStrategy}. Returns
     * {@code true} if any action were taken, {@code false} if not.
     */
    public boolean changeBackgroundColorForResizing() {
        if (mCustomTabHeightStrategy == null) return false;

        return mCustomTabHeightStrategy.changeBackgroundColorForResizing();
    }

    /**
     * Perform slide-down animation on closing.
     * @param finishRunnable Runnable finishing the activity after the animation.
     */
    void handleCloseAnimation(Runnable finishRunnable) {
        mCustomTabHeightStrategy.handleCloseAnimation(finishRunnable);
    }

    private boolean isAdsNoticeInCCTFeatureEnabled() {
        return ChromeFeatureList.isEnabled(ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT)
                && (ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
                                ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
                                "include-mode-b",
                                false)
                        || !ChromeFeatureList.isEnabled(
                                ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING));
    }

    /** Runs a set of deferred startup tasks. */
    void onDeferredStartup() {
        new OneShotCallback<>(
                mProfileSupplier,
                mCallbackController.makeCancelable(
                        (profile) -> {
                            Profile regularProfile = profile.getOriginalProfile();
                            Profile currentModelProfile =
                                    mTabModelSelectorSupplier.get().getCurrentModel().getProfile();

                            boolean didShowPrompt = false;
                            boolean shouldShowPrivacySandboxDialog =
                                    PrivacySandboxDialogController.shouldShowPrivacySandboxDialog(
                                            currentModelProfile, SurfaceType.AGACCT);
                            int activityType = mIntentDataProvider.get().getActivityType();
                            boolean isCustomTab =
                                    activityType == ActivityType.CUSTOM_TAB
                                            && !(mIntentDataProvider.get().isPartialCustomTab());
                            if (isCustomTab) {
                                RecordHistogram.recordBooleanHistogram(
                                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT",
                                        shouldShowPrivacySandboxDialog);
                            }

                            if (isAdsNoticeInCCTFeatureEnabled()
                                    && shouldShowPrivacySandboxDialog
                                    && isCustomTab) {
                                boolean shouldShowPrivacySandboxDialogAppIdCheck = true;
                                String appId = mIntentDataProvider.get().getClientPackageName();
                                String paramAdsNoticeAppId =
                                        ChromeFeatureList.getFieldTrialParamByFeature(
                                                ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
                                                "app-id");
                                if (!paramAdsNoticeAppId.isEmpty()
                                        && !paramAdsNoticeAppId.equals(appId)) {
                                    shouldShowPrivacySandboxDialogAppIdCheck = false;
                                }
                                RecordHistogram.recordBooleanHistogram(
                                        "Startup.Android.PrivacySandbox.AdsNoticeCCTAppIDCheck",
                                        shouldShowPrivacySandboxDialogAppIdCheck);
                                if (shouldShowPrivacySandboxDialogAppIdCheck) {
                                    didShowPrompt =
                                            PrivacySandboxDialogController
                                                    .maybeLaunchPrivacySandboxDialog(
                                                            mActivity,
                                                            currentModelProfile,
                                                            SurfaceType.AGACCT);
                                }
                            }
                            if (!didShowPrompt) {
                                didShowPrompt =
                                        RequestDesktopUtils
                                                .maybeShowDefaultEnableGlobalSettingMessage(
                                                        regularProfile,
                                                        mMessageDispatcher,
                                                        mActivity);
                            }
                            if (!didShowPrompt && mAppMenuCoordinator != null) {
                                mDesktopSiteSettingsIPHController =
                                        DesktopSiteSettingsIPHController.create(
                                                mActivity,
                                                mWindowAndroid,
                                                mActivityTabProvider,
                                                regularProfile,
                                                getToolbarManager().getMenuButtonView(),
                                                mAppMenuCoordinator.getAppMenuHandler());
                            }

                            if (!didShowPrompt
                                    && ChromeFeatureList.isEnabled(
                                            ChromeFeatureList
                                                    .TRACKING_PROTECTION_USER_BYPASS_PWA_TRIGGER)
                                    && mActivityType == ActivityType.WEB_APK
                                    && mTrackingProtectionSnackbarController != null) {
                                mTrackingProtectionSnackbarController.maybeTriggerSnackbar();
                            }
                        }));
        SupplierUtils.waitForAll(
                () -> maybeRecordPrivacySandboxActivityType(),
                mIntentDataProvider,
                mProfileSupplier);
    }

    private void maybeRecordPrivacySandboxActivityType() {
        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.PRIVACY_SANDBOX_ACTIVITY_TYPE_STORAGE)) {
            return;
        }

        int privacySandboxStorageActivityType =
                ActivityTypeMapper.toPrivacySandboxStorageActivityType(
                        mActivityType, mIntentDataProvider.get());

        PrivacySandboxBridge privacySandboxBridge =
                new PrivacySandboxBridge(mProfileSupplier.get());
        privacySandboxBridge.recordActivityType(privacySandboxStorageActivityType);
    }

    private Runnable getPageInfoSnackbarOnAction() {
        return () ->
                new ChromePageInfo(
                                mModalDialogManagerSupplier,
                                null,
                                OpenedFromSource.WEBAPK_SNACKBAR,
                                getMerchantTrustSignalsCoordinatorSupplier()::get,
                                getEphemeralTabCoordinatorSupplier(),
                                mTabCreatorManagerSupplier
                                        .get()
                                        .getTabCreator(mProfileSupplier.get().isOffTheRecord()))
                        .show(mActivityTabProvider.get(), ChromePageInfoHighlight.noHighlight());
    }

    CustomTabHeightStrategy getCustomTabSizeStrategyForTesting() {
        return mCustomTabHeightStrategy;
    }
}