chromium/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java

// Copyright 2015 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.features.toolbar;

import static androidx.browser.customtabs.CustomTabsIntent.CLOSE_BUTTON_POSITION_END;

import static org.chromium.base.MathUtils.interpolate;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.ColorInt;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.browser.customtabs.CustomTabsIntent.CloseButtonPosition;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.widget.ImageViewCompat;

import org.chromium.base.Callback;
import org.chromium.base.CallbackController;
import org.chromium.base.ObserverList;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.customtabs.CustomTabFeatureOverridesManager;
import org.chromium.chrome.browser.customtabs.features.branding.ToolbarBrandingDelegate;
import org.chromium.chrome.browser.customtabs.features.branding.ToolbarBrandingOverlayCoordinator;
import org.chromium.chrome.browser.customtabs.features.branding.ToolbarBrandingOverlayProperties;
import org.chromium.chrome.browser.customtabs.features.minimizedcustomtab.CustomTabMinimizeDelegate;
import org.chromium.chrome.browser.customtabs.features.minimizedcustomtab.MinimizedFeatureUtils;
import org.chromium.chrome.browser.ephemeraltab.EphemeralTabCoordinator;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.omnibox.LocationBarDataProvider;
import org.chromium.chrome.browser.omnibox.OmniboxStub;
import org.chromium.chrome.browser.omnibox.UrlBar;
import org.chromium.chrome.browser.omnibox.UrlBarCoordinator;
import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
import org.chromium.chrome.browser.omnibox.UrlBarData;
import org.chromium.chrome.browser.omnibox.status.PageInfoIPHController;
import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsVisualState;
import org.chromium.chrome.browser.page_info.ChromePageInfo;
import org.chromium.chrome.browser.page_info.ChromePageInfoHighlight;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.searchwidget.SearchActivityClientImpl;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TrustedCdn;
import org.chromium.chrome.browser.tabmodel.TabCreator;
import org.chromium.chrome.browser.theme.ThemeUtils;
import org.chromium.chrome.browser.toolbar.LocationBarModel;
import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
import org.chromium.chrome.browser.toolbar.ToolbarProgressBar;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButton;
import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult;
import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
import org.chromium.chrome.browser.toolbar.top.ToolbarPhone;
import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotDifference;
import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarColorObserver;
import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
import org.chromium.chrome.browser.user_education.UserEducationHelper;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.widget.TintedDrawable;
import org.chromium.components.content_settings.CookieBlocking3pcdStatus;
import org.chromium.components.content_settings.CookieControlsBridge;
import org.chromium.components.content_settings.CookieControlsObserver;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.page_info.PageInfoController.OpenedFromSource;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.content_public.browser.BrowserContextHandle;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.ContentUrlConstants;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.Clipboard;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.interpolators.Interpolators;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.text.SpanApplier.SpanInfo;
import org.chromium.ui.widget.Toast;
import org.chromium.url.GURL;

import java.util.Optional;
import java.util.function.Consumer;

/** The Toolbar layout to be used for a custom tab. This is used for both phone and tablet UIs. */
public class CustomTabToolbar extends ToolbarLayout implements View.OnLongClickListener {
    private static final Object ORIGIN_SPAN = new Object();
    private ImageView mIncognitoImageView;
    private LinearLayout mCustomActionButtons;
    private LinearLayout mCloseMinimizeLayout;
    private ImageButton mCloseButton;
    private ImageButton mMinimizeButton;
    private MenuButton mMenuButton;
    // This View will be non-null only for bottom sheet custom tabs.
    private Drawable mHandleDrawable;

    // Color scheme and tint that will be applied to icons and text.
    private @BrandedColorScheme int mBrandedColorScheme;
    private ColorStateList mTint;

    private ValueAnimator mBrandColorTransitionAnimation;
    private boolean mBrandColorTransitionActive;

    private GURL mFirstUrl;

    private final CustomTabLocationBar mLocationBar = new CustomTabLocationBar();
    private LocationBarModel mLocationBarModel;
    private BrowserStateBrowserControlsVisibilityDelegate mBrowserControlsVisibilityDelegate;
    private @Nullable CustomTabCaptureStateToken mLastCustomTabCaptureStateToken;
    private ObserverList<Callback<Integer>> mContainerVisibilityChangeObserverList =
            new ObserverList<>();
    private @Nullable CustomTabFeatureOverridesManager mFeatureOverridesManager;

    // Whether the maximization button should be shown when it can. Set to {@code true}
    // while the side sheet is running with the maximize button option on.
    private boolean mMaximizeButtonEnabled;
    private boolean mMinimizeButtonEnabled;

    private OnClickListener mCloseClickListener;
    private CookieControlsBridge mCookieControlsBridge;
    private boolean mShouldHighlightCookieControlsIcon;
    private boolean mCookieControlsVisible;
    private boolean mThirdPartyCookiesBlocked;
    private int mBlockingStatus3pcd;

    private final Handler mTaskHandler = new Handler();

    // The resource ID of the most recently set security icon. Used for testing since
    // VectorDrawables can't be straightforwardly tested for equality..
    private int mSecurityIconResourceForTesting;

    /** Whether to use the toolbar as handle to resize the Window height. */
    public interface HandleStrategy {
        /**
         * Decide whether we need to intercept the touch events so the events will be passed to the
         * {@link #onTouchEvent()} method.
         *
         * @param event The touch event to be examined.
         * @return whether the event will be passed to {@link #onTouchEvent()}.
         */
        boolean onInterceptTouchEvent(MotionEvent event);

        /**
         * Handling the touch events.
         *
         * @param event The touch event to be handled.
         * @return whether the event is consumed..
         */
        boolean onTouchEvent(MotionEvent event);

        /**
         * Set a handler to close the current tab.
         *
         * @param handler The handler for closing the current tab.
         */
        void setCloseClickHandler(Runnable handler);
    }

    private HandleStrategy mHandleStrategy;
    private @CloseButtonPosition int mCloseButtonPosition;

    /** Callback used to notify the maximize button on side sheet PCCT click event. */
    public interface MaximizeButtonCallback {
        /**
         * @return {@code true} if the PCCT gets maximized. {@code false} if restored.
         */
        boolean onClick();
    }

    /** Constructor for getting this class inflated from an xml layout file. */
    public CustomTabToolbar(Context context, AttributeSet attrs) {
        super(context, attrs);

        mTint = ChromeColors.getPrimaryIconTint(getContext(), false);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        final int backgroundColor = ChromeColors.getDefaultThemeColor(getContext(), false);
        setBackground(new ColorDrawable(backgroundColor));
        mBrandedColorScheme = BrandedColorScheme.APP_DEFAULT;

        mIncognitoImageView = findViewById(R.id.incognito_cct_logo_image_view);
        mCustomActionButtons = findViewById(R.id.action_buttons);
        mCloseButton = findViewById(R.id.close_button);
        mCloseButton.setOnLongClickListener(this);
        mCloseMinimizeLayout = findViewById(R.id.close_minimize_layout);
        mMenuButton = findViewById(R.id.menu_button_wrapper);

        mLocationBar.onFinishInflate(this);

        if (!ChromeFeatureList.sCctIntentFeatureOverrides.isEnabled()) {
            maybeInitMinimizeButton();
        }

        // Set hover tooltip texts for toolbar buttons.
        super.setTooltipTextForToolbarButtons();
    }

    @Override
    public void onNativeLibraryReady() {
        super.onNativeLibraryReady();
        mLocationBar.onNativeLibraryReady();
    }

    @Override
    protected void setCloseButtonImageResource(Drawable drawable) {
        mCloseButton.setVisibility(drawable != null ? View.VISIBLE : View.GONE);
        mCloseButton.setImageDrawable(drawable);
        if (drawable != null) {
            updateButtonTint(mCloseButton);
        }
    }

    @Override
    protected void setCustomTabCloseClickHandler(OnClickListener listener) {
        mCloseButton.setOnClickListener(listener);
    }

    @Override
    protected void addCustomActionButton(
            Drawable drawable, String description, OnClickListener listener) {
        ImageButton button =
                (ImageButton)
                        LayoutInflater.from(getContext())
                                .inflate(
                                        R.layout.custom_tabs_toolbar_button,
                                        mCustomActionButtons,
                                        false);
        button.setOnLongClickListener(this);
        button.setOnClickListener(listener);
        button.setVisibility(VISIBLE);

        updateCustomActionButtonVisuals(button, drawable, description);

        // Add the view at the beginning of the child list.
        mCustomActionButtons.addView(button, 0);
    }

    @Override
    protected void updateCustomActionButton(int index, Drawable drawable, String description) {
        ImageButton button =
                (ImageButton)
                        mCustomActionButtons.getChildAt(
                                mCustomActionButtons.getChildCount() - 1 - index);
        assert button != null;
        updateCustomActionButtonVisuals(button, drawable, description);
    }

    /**
     * Creates and returns a CustomTab-specific LocationBar. This also retains a reference to the
     * passed LocationBarModel.
     * @param locationBarModel {@link LocationBarModel} to be used for accessing LocationBar
     *         state.
     * @param actionModeCallback Callback to handle changes in contextual action Modes.
     * @param modalDialogManagerSupplier Supplier of {@link ModalDialogManager}.
     * @param ephemeralTabCoordinatorSupplier Supplier of {@link EphemeralTabCoordinator}.
     * @param controlsVisibilityDelegate {@link BrowserStateBrowserControlsVisibilityDelegate} to
     *         show / hide the browser control. Used to ensure toolbar is shown for a certain
     *         duration.
     * @param tabCreator {@link TabCreator} to handle a new tab creation.
     * @return The LocationBar implementation for this CustomTabToolbar.
     */
    public LocationBar createLocationBar(
            LocationBarModel locationBarModel,
            ActionMode.Callback actionModeCallback,
            Supplier<ModalDialogManager> modalDialogManagerSupplier,
            Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
            BrowserStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate,
            TabCreator tabCreator) {
        mLocationBarModel = locationBarModel;
        mLocationBar.init(
                locationBarModel,
                modalDialogManagerSupplier,
                ephemeralTabCoordinatorSupplier,
                tabCreator,
                actionModeCallback);
        mBrowserControlsVisibilityDelegate = controlsVisibilityDelegate;
        return mLocationBar;
    }

    /**
     * Initialize the maximize button for side sheet CCT. Create one if not instantiated.
     *
     * @param maximizedOnInit {@code true} if the side sheet is starting in maximized state.
     * @param onMaximizeClicked Callback to invoke when maximize button gets clicked.
     */
    public void initSideSheetMaximizeButton(
            boolean maximizedOnInit, MaximizeButtonCallback callback) {
        ImageButton maximizeButton = findViewById(R.id.custom_tabs_sidepanel_maximize);
        if (maximizeButton == null) {
            ViewStub maximizeButtonStub = findViewById(R.id.maximize_button_stub);
            maximizeButtonStub.inflate();
            maximizeButton = findViewById(R.id.custom_tabs_sidepanel_maximize);
        }
        mMaximizeButtonEnabled = true;
        setMaximizeButtonDrawable(maximizedOnInit);
        maximizeButton.setOnClickListener((v) -> setMaximizeButtonDrawable(callback.onClick()));

        // The visibility will set after the location bar completes its layout. But there are
        // cases where the location bar layout gets already completed. Trigger the visibility
        // update manually here.
        setMaximizeButtonVisibility();
    }

    public void setFeatureOverridesManager(CustomTabFeatureOverridesManager manager) {
        mFeatureOverridesManager = manager;

        maybeInitMinimizeButton();
    }

    /**
     * Sets the {@link CustomTabMinimizeDelegate} to allow the toolbar to minimize the tab.
     *
     * @param delegate The {@link CustomTabMinimizeDelegate}.
     */
    public void setMinimizeDelegate(@NonNull CustomTabMinimizeDelegate delegate) {
        mMinimizeButton.setOnClickListener(view -> delegate.minimize());
    }

    /**
     * Enables the interactive Omnibox in CCT.
     *
     * @param clientPackageName the package name of the custom tabs embedder.
     * @param tapHandler a handler for taps on the omnibox, or null if the default handler should be
     *     used.
     */
    public void setOmniboxEnabled(String clientPackageName, @Nullable Consumer<Tab> tapHandler) {
        mLocationBar.setOmniboxEnabled(clientPackageName, tapHandler);
    }

    private void setButtonsVisibility() {
        setMaximizeButtonVisibility();
        setMinimizeButtonVisibility();
    }

    private void setMaximizeButtonVisibility() {
        ImageButton maximizeButton = findViewById(R.id.custom_tabs_sidepanel_maximize);
        if (!mMaximizeButtonEnabled || maximizeButton == null) {
            if (maximizeButton != null) maximizeButton.setVisibility(View.GONE);
            setUrlTitleBarMargin(0);
            return;
        }

        // Find the title/url width threshold that turns the maximize button visible.
        int containerWidthPx = mLocationBar.mTitleUrlContainer.getWidth();
        if (containerWidthPx == 0) return;

        int maximizeButtonWidthPx =
                getResources().getDimensionPixelSize(R.dimen.location_bar_action_icon_width);
        int titleUrlPaddingEndPx =
                getResources().getDimensionPixelSize(R.dimen.toolbar_edge_padding);
        if (containerWidthPx < maximizeButtonWidthPx * 2 - titleUrlPaddingEndPx) {
            // We expect to see at least as much URL text as the width of the maximize button.
            // Hide the button if we can't.
            maximizeButton.setVisibility(View.GONE);
        } else {
            mLocationBar.removeButtonsVisibilityUpdater();

            // Take some space from the title/url for maximization button.
            setUrlTitleBarMargin(maximizeButtonWidthPx);
            maximizeButton.setVisibility(View.VISIBLE);
        }
    }

    private void setUrlTitleBarMargin(int margin) {
        setViewRightMargin(mLocationBar.mTitleBar, margin);
        setViewRightMargin(mLocationBar.mUrlBar, margin);
    }

    private static void setViewRightMargin(View view, int margin) {
        if (view == null) return;
        var lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        if (lp != null && lp.rightMargin != margin) {
            lp.rightMargin = margin;
            view.setLayoutParams(lp);
        }
    }

    private void setMaximizeButtonDrawable(boolean maximized) {
        @DrawableRes
        int drawableId = maximized ? R.drawable.ic_fullscreen_exit : R.drawable.ic_fullscreen_enter;
        int buttonDescId =
                maximized
                        ? R.string.custom_tab_side_sheet_minimize
                        : R.string.custom_tab_side_sheet_maximize;
        ImageButton maximizeButton = findViewById(R.id.custom_tabs_sidepanel_maximize);
        var d = UiUtils.getTintedDrawable(getContext(), drawableId, mTint);
        updateCustomActionButtonVisuals(maximizeButton, d, getResources().getString(buttonDescId));
    }

    /** Remove maximize button from side sheet CCT toolbar. */
    public void removeSideSheetMaximizeButton() {
        ImageButton maximizeButton = findViewById(R.id.custom_tabs_sidepanel_maximize);
        maximizeButton.setOnClickListener(null);
        maximizeButton.setVisibility(View.GONE);
        mMaximizeButtonEnabled = false;
    }

    @VisibleForTesting
    void maybeInitMinimizeButton() {
        if (!MinimizedFeatureUtils.isMinimizedCustomTabAvailable(
                getContext(), mFeatureOverridesManager)) {
            return;
        }

        ViewStub minimizeButtonStub = findViewById(R.id.minimize_button_stub);
        minimizeButtonStub.inflate();
        ImageButton minimizeButton = findViewById(R.id.custom_tabs_minimize_button);
        var d =
                UiUtils.getTintedDrawable(
                        getContext(), MinimizedFeatureUtils.getMinimizeIcon(), mTint);
        minimizeButton.setTag(R.id.custom_tabs_toolbar_tintable, true);
        minimizeButton.setImageDrawable(d);
        updateButtonTint(minimizeButton);
        minimizeButton.setOnLongClickListener(this);
        mMinimizeButtonEnabled = true;
        mMinimizeButton = minimizeButton;
    }

    private void setMinimizeButtonVisibility() {
        if (mMinimizeButton == null) return;

        if (!mMinimizeButtonEnabled || isInMultiWindowMode()) {
            if (mMinimizeButton.getVisibility() != View.GONE) {
                mMinimizeButton.setVisibility(View.GONE);
                maybeAdjustButtonSpacingForCloseButtonPosition();
            }
            return;
        }

        // Find the title/url width threshold that turns the minimize button visible.
        int containerWidthPx = mLocationBar.mTitleUrlContainer.getWidth();
        int minUrlWidthPx =
                getResources().getDimensionPixelSize(R.dimen.location_bar_min_url_width);
        if (containerWidthPx == 0) return;
        if (containerWidthPx < minUrlWidthPx) {
            // We expect to see at least as much URL text as the width of the minimize button.
            // Hide the button if we can't.
            mMinimizeButton.setVisibility(View.GONE);
        } else {
            mMinimizeButton.setVisibility(View.VISIBLE);
            mLocationBar.removeButtonsVisibilityUpdater();
        }
        updateToolbarLayoutMargin();
    }

    private boolean isInMultiWindowMode() {
        Tab currentTab = getCurrentTab();
        if (currentTab == null) return false;

        Activity activity = currentTab.getWindowAndroid().getActivity().get();
        return MultiWindowUtils.getInstance().isInMultiWindowMode(activity);
    }

    private void updateCustomActionButtonVisuals(
            ImageButton button, Drawable drawable, String description) {
        Resources resources = getResources();

        // The height will be scaled to match spec while keeping the aspect ratio, so get the scaled
        // width through that.
        int sourceHeight = drawable.getIntrinsicHeight();
        int sourceScaledHeight = resources.getDimensionPixelSize(R.dimen.toolbar_icon_height);
        int sourceWidth = drawable.getIntrinsicWidth();
        int sourceScaledWidth = sourceWidth * sourceScaledHeight / sourceHeight;
        int minPadding = resources.getDimensionPixelSize(R.dimen.min_toolbar_icon_side_padding);

        int sidePadding = Math.max((2 * sourceScaledHeight - sourceScaledWidth) / 2, minPadding);
        int topPadding = button.getPaddingTop();
        int bottomPadding = button.getPaddingBottom();
        button.setPadding(sidePadding, topPadding, sidePadding, bottomPadding);
        button.setImageDrawable(drawable);
        updateButtonTint(button);

        button.setContentDescription(description);
    }

    public void setMinimizeButtonEnabled(boolean enabled) {
        mMinimizeButtonEnabled = enabled;
        setMinimizeButtonVisibility();
    }

    /**
     * @return The custom action button with the given {@code index}. For test purpose only.
     * @param index The index of the custom action button to return.
     */
    public ImageButton getCustomActionButtonForTest(int index) {
        return (ImageButton) mCustomActionButtons.getChildAt(index);
    }

    public ImageButton getMaximizeButtonForTest() {
        return findViewById(R.id.custom_tabs_sidepanel_maximize);
    }

    @Override
    protected int getTabStripHeightFromResource() {
        return 0;
    }

    /** @return The current active {@link Tab}. */
    private @Nullable Tab getCurrentTab() {
        return getToolbarDataProvider().getTab();
    }

    @Override
    public void setUrlBarHidden(boolean hideUrlBar) {
        mLocationBar.setUrlBarHidden(hideUrlBar);
    }

    @Override
    protected void onNavigatedToDifferentPage() {
        super.onNavigatedToDifferentPage();
        mLocationBarModel.notifyTitleChanged();
        if (mLocationBar.isShowingTitleOnly()) {
            if (mFirstUrl == null || mFirstUrl.isEmpty()) {
                mFirstUrl = getToolbarDataProvider().getTab().getUrl();
            } else {
                if (mFirstUrl.equals(getToolbarDataProvider().getTab().getUrl())) return;
                setUrlBarHidden(false);
            }
        }
        mLocationBarModel.notifySecurityStateChanged();
    }

    @Override
    @SuppressLint("ClickableViewAccessibility")
    public boolean onTouchEvent(MotionEvent event) {
        if (mHandleStrategy != null) {
            return mHandleStrategy.onTouchEvent(event);
        }
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mHandleStrategy != null) {
            return mHandleStrategy.onInterceptTouchEvent(event);
        }
        return false;
    }

    public void setHandleStrategy(HandleStrategy strategy) {
        mHandleStrategy = strategy;
        mHandleStrategy.setCloseClickHandler(mCloseButton::callOnClick);
    }

    /**
     * Sets the close button position for this toolbar.
     *
     * @param closeButtonPosition The {@link CloseButtonPosition}.
     */
    public void setCloseButtonPosition(@CloseButtonPosition int closeButtonPosition) {
        mCloseButtonPosition = closeButtonPosition;
    }

    private void updateButtonsTint() {
        updateButtonTint(mCloseButton);
        if (mMinimizeButton != null) {
            updateButtonTint(mMinimizeButton);
        }
        int numCustomActionButtons = mCustomActionButtons.getChildCount();
        for (int i = 0; i < numCustomActionButtons; i++) {
            updateButtonTint((ImageButton) mCustomActionButtons.getChildAt(i));
        }
        ImageButton maximizeButton = findViewById(R.id.custom_tabs_sidepanel_maximize);
        if (maximizeButton != null) updateButtonTint(maximizeButton);
        updateButtonTint(mLocationBar.getSecurityButton());
    }

    private void updateButtonTint(ImageButton button) {
        Drawable drawable = button.getDrawable();
        if (drawable instanceof TintedDrawable tintedDrawable) {
            tintedDrawable.setTint(mTint);
        } else if (button.getTag(R.id.custom_tabs_toolbar_tintable) != null) {
            drawable.setTintList(mTint);
        }
    }

    private void maybeSwapCloseAndMenuButtons() {
        if (mCloseButtonPosition != CLOSE_BUTTON_POSITION_END) return;

        final View closeButton = findViewById(R.id.close_button);
        final View minButton = findViewById(R.id.custom_tabs_minimize_button);
        final ViewGroup closeMinButton = (ViewGroup) closeButton.getParent();
        final int closeButtonIndex = indexOfChild(closeButton);
        final View menuButton = findViewById(R.id.menu_button_wrapper);
        final int menuButtonIndex = indexOfChild(menuButton);
        final ViewGroup.LayoutParams menuButtonLayoutParams = menuButton.getLayoutParams();
        removeViewAt(menuButtonIndex);
        addView(menuButton, closeButtonIndex, menuButtonLayoutParams);
        closeMinButton.removeView(closeButton);
        if (minButton != null) {
            closeMinButton.removeView(minButton);
        }

        if (MinimizedFeatureUtils.isMinimizedCustomTabAvailable(
                        getContext(), mFeatureOverridesManager)
                && minButton != null) {
            closeMinButton.addView(minButton);
        }
        closeMinButton.addView(closeButton);
    }

    private void maybeAdjustButtonSpacingForCloseButtonPosition() {
        if (mCloseButtonPosition != CLOSE_BUTTON_POSITION_END) return;

        final @Dimension int buttonWidth =
                getResources().getDimensionPixelSize(R.dimen.toolbar_button_width);
        final FrameLayout.LayoutParams menuButtonLayoutParams =
                (FrameLayout.LayoutParams) mMenuButton.getLayoutParams();
        menuButtonLayoutParams.width = buttonWidth;
        menuButtonLayoutParams.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
        mMenuButton.setLayoutParams(menuButtonLayoutParams);
        mMenuButton.setPaddingRelative(0, 0, 0, 0);

        FrameLayout.LayoutParams closeMinLayout =
                (FrameLayout.LayoutParams) mCloseMinimizeLayout.getLayoutParams();
        closeMinLayout.gravity = Gravity.CENTER_VERTICAL | Gravity.END;
        closeMinLayout.setMarginStart(closeMinLayout.getMarginEnd());
        closeMinLayout.setMarginEnd(0);
        mCloseMinimizeLayout.setLayoutParams(closeMinLayout);

        FrameLayout.LayoutParams actionButtonsLayoutParams =
                (FrameLayout.LayoutParams) mCustomActionButtons.getLayoutParams();
        if (MinimizedFeatureUtils.isMinimizedCustomTabAvailable(
                getContext(), mFeatureOverridesManager)) {
            actionButtonsLayoutParams.setMarginEnd(
                    mMinimizeButton == null || mMinimizeButton.getVisibility() == View.GONE
                            ? buttonWidth
                            : buttonWidth * 2);
            var lpTitle = (ViewGroup.MarginLayoutParams) mLocationBar.mTitleBar.getLayoutParams();
            var lpUrl = (ViewGroup.MarginLayoutParams) mLocationBar.mUrlBar.getLayoutParams();
            LayoutParams lp = (LayoutParams) mLocationBar.getLayout().getLayoutParams();
            // Prevent URL and title from bleeding over minimize button
            lpTitle.setMarginEnd(buttonWidth);
            lpUrl.setMarginEnd(buttonWidth);
            lp.setMarginStart(buttonWidth);
            if (getResources().getConfiguration().getLayoutDirection()
                    == View.LAYOUT_DIRECTION_RTL) {
                var lpSecurity =
                        (ViewGroup.MarginLayoutParams)
                                mLocationBar.getSecurityIconView().getLayoutParams();
                var lpTitleUrlContainer =
                        (ViewGroup.MarginLayoutParams)
                                mLocationBar.mTitleUrlContainer.getLayoutParams();
                lpTitle.setMarginEnd(0);
                lpUrl.setMarginEnd(0);
                if (mMinimizeButton != null && mMinimizeButton.getVisibility() != View.GONE) {
                    lpSecurity.leftMargin = buttonWidth;
                    lpTitleUrlContainer.leftMargin += buttonWidth;
                    mLocationBar.mTitleUrlContainer.setLayoutParams(lpTitleUrlContainer);
                } else {
                    // No minimize button, don't need the extra affordance since minimize button is
                    // gone
                    lpSecurity.leftMargin = 0;
                    mLocationBar.updateLeftMarginOfTitleUrlContainer();
                }
                mLocationBar.getSecurityIconView().setLayoutParams(lpSecurity);
            }
            mLocationBar.getLayout().setLayoutParams(lp);
            mLocationBar.mTitleBar.setLayoutParams(lpTitle);
            mLocationBar.mUrlBar.setLayoutParams(lpUrl);
        } else {
            actionButtonsLayoutParams.setMarginEnd(buttonWidth);
        }
        mCustomActionButtons.setLayoutParams(actionButtonsLayoutParams);
    }

    private void updateToolbarLayoutMargin() {
        final boolean shouldShowIncognitoIcon = isIncognitoBranded();
        mIncognitoImageView.setVisibility(shouldShowIncognitoIcon ? VISIBLE : GONE);

        int startMargin = calculateStartMarginForStartButtonVisibility();

        updateStartMarginOfVisibleElementsUntilLocationBarFrameLayout(startMargin);

        int locationBarLayoutChildIndex = getLocationBarFrameLayoutIndex();
        assert locationBarLayoutChildIndex != -1;
        updateLocationBarLayoutEndMargin(locationBarLayoutChildIndex);

        // Update left margin of mTitleUrlContainer here to make sure the security icon is
        // always placed left of the urlbar.
        mLocationBar.updateLeftMarginOfTitleUrlContainer();
    }

    private int calculateStartMarginForStartButtonVisibility() {
        final View buttonAtStart =
                mCloseButtonPosition == CLOSE_BUTTON_POSITION_END ? mMenuButton : mCloseButton;
        return (buttonAtStart.getVisibility() == GONE)
                ? getResources()
                        .getDimensionPixelSize(
                                R.dimen.custom_tabs_toolbar_horizontal_margin_no_start)
                : 0;
    }

    private void updateStartMarginOfVisibleElementsUntilLocationBarFrameLayout(int startMargin) {
        int locationBarFrameLayoutIndex = getLocationBarFrameLayoutIndex();
        for (int i = 0; i < locationBarFrameLayoutIndex; ++i) {
            View childView = getChildAt(i);
            if (childView.getVisibility() == GONE) continue;

            updateViewLayoutParams(childView, startMargin);

            LayoutParams childLayoutParams = (LayoutParams) childView.getLayoutParams();
            int widthMeasureSpec = calcWidthMeasure(childLayoutParams);
            int heightMeasureSpec = calcHeightMeasure(childLayoutParams);
            childView.measure(widthMeasureSpec, heightMeasureSpec);
            int width = childView.getMeasuredWidth();
            // close_minimize_layout is the first child view in the toolbar.
            // It includes two buttons when minimized is enabled, but when they are positioned at
            // the end our start margin is doubly large.
            if (mMinimizeButtonEnabled
                    && mCloseButtonPosition == CLOSE_BUTTON_POSITION_END
                    && i == 0) {
                width /= 2;
            }
            startMargin += width;
        }
        updateStartMarginOfLocationBarFrameLayout(startMargin);
    }

    private void updateStartMarginOfLocationBarFrameLayout(int startMargin) {
        int locationBarFrameLayoutIndex = getLocationBarFrameLayoutIndex();
        View locationBarLayoutView = getChildAt(locationBarFrameLayoutIndex);
        updateViewLayoutParams(locationBarLayoutView, startMargin);
    }

    private void updateViewLayoutParams(View view, int margin) {
        LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
        if (MarginLayoutParamsCompat.getMarginStart(layoutParams) != margin) {
            MarginLayoutParamsCompat.setMarginStart(layoutParams, margin);
            view.setLayoutParams(layoutParams);
        }
    }

    private void updateLocationBarLayoutEndMargin(int startIndex) {
        int locationBarLayoutEndMargin = 0;
        for (int i = startIndex + 1; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            if (childView.getVisibility() != GONE) {
                locationBarLayoutEndMargin += childView.getMeasuredWidth();
            }
        }
        LayoutParams urlLayoutParams = (LayoutParams) mLocationBar.getLayout().getLayoutParams();

        if (MarginLayoutParamsCompat.getMarginEnd(urlLayoutParams) != locationBarLayoutEndMargin) {
            MarginLayoutParamsCompat.setMarginEnd(urlLayoutParams, locationBarLayoutEndMargin);
            mLocationBar.getLayout().setLayoutParams(urlLayoutParams);
        }
    }

    private int getLocationBarFrameLayoutIndex() {
        assert mLocationBar.getLayout().getVisibility() != GONE;
        for (int i = 0; i < getChildCount(); i++) {
            if (getChildAt(i) == mLocationBar.getLayout()) return i;
        }
        return -1;
    }

    private int calcWidthMeasure(LayoutParams childLayoutParams) {
        if (childLayoutParams.width == LayoutParams.WRAP_CONTENT) {
            return MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.AT_MOST);
        }

        if (childLayoutParams.width == LayoutParams.MATCH_PARENT) {
            return MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
        }

        return MeasureSpec.makeMeasureSpec(childLayoutParams.width, MeasureSpec.EXACTLY);
    }

    private int calcHeightMeasure(LayoutParams childLayoutParams) {
        if (childLayoutParams.height == LayoutParams.WRAP_CONTENT) {
            return MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.AT_MOST);
        }

        if (childLayoutParams.height == LayoutParams.MATCH_PARENT) {
            return MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY);
        }

        return MeasureSpec.makeMeasureSpec(childLayoutParams.height, MeasureSpec.EXACTLY);
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mLocationBar.addButtonsVisibilityUpdater();
        mLocationBarModel.notifyTitleChanged();
        mLocationBarModel.notifyUrlChanged();
        mLocationBarModel.notifyPrimaryColorChanged();
    }

    @Override
    public ColorDrawable getBackground() {
        return (ColorDrawable) super.getBackground();
    }

    /**
     * For extending classes to override and carry out the changes related with the primary color
     * for the current tab changing.
     */
    @Override
    public void onPrimaryColorChanged(boolean shouldAnimate) {
        if (mBrandColorTransitionActive) mBrandColorTransitionAnimation.cancel();

        final ColorDrawable background = getBackground();
        final int startColor = background.getColor();
        final int endColor = getToolbarDataProvider().getPrimaryColor();

        if (background.getColor() == endColor) return;

        mBrandColorTransitionAnimation =
                ValueAnimator.ofFloat(0, 1)
                        .setDuration(ToolbarPhone.THEME_COLOR_TRANSITION_DURATION);
        mBrandColorTransitionAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
        mBrandColorTransitionAnimation.addUpdateListener(
                animation -> {
                    float fraction = animation.getAnimatedFraction();
                    int red =
                            (int) interpolate(Color.red(startColor), Color.red(endColor), fraction);
                    int blue =
                            (int)
                                    interpolate(
                                            Color.blue(startColor), Color.blue(endColor), fraction);
                    int green =
                            (int)
                                    interpolate(
                                            Color.green(startColor),
                                            Color.green(endColor),
                                            fraction);
                    int color = Color.rgb(red, green, blue);
                    background.setColor(color);
                    notifyToolbarColorChanged(color);
                    setHandleViewBackgroundColor(color);
                });
        mBrandColorTransitionAnimation.addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mBrandColorTransitionActive = false;

                        // Using the current background color instead of the final color in case
                        // this
                        // animation was cancelled.  This ensures the assets are updated to the
                        // visible color.
                        updateColorsForBackground(background.getColor());
                    }
                });
        mBrandColorTransitionAnimation.start();
        mBrandColorTransitionActive = true;
        if (!shouldAnimate) mBrandColorTransitionAnimation.end();
    }

    private void updateColorsForBackground(@ColorInt int background) {
        final @BrandedColorScheme int brandedColorScheme =
                OmniboxResourceProvider.getBrandedColorScheme(
                        getContext(), isIncognitoBranded(), background);
        if (mBrandedColorScheme == brandedColorScheme) return;
        mBrandedColorScheme = brandedColorScheme;
        final ColorStateList tint =
                ThemeUtils.getThemedToolbarIconTint(getContext(), mBrandedColorScheme);
        mTint = tint;
        mLocationBar.updateColors();
        setToolbarHairlineColor(background);
        notifyToolbarColorChanged(background);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        maybeSwapCloseAndMenuButtons();
        updateToolbarLayoutMargin();
        maybeAdjustButtonSpacingForCloseButtonPosition();
        setMaximizeButtonVisibility();
        setMinimizeButtonVisibility();

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public LocationBar getLocationBar() {
        return mLocationBar;
    }

    /** Return the delegate used to control branding UI changes on the location bar. */
    public ToolbarBrandingDelegate getBrandingDelegate() {
        return mLocationBar;
    }

    public void setHandleBackground(Drawable handleDrawable) {
        mHandleDrawable = handleDrawable;
        setHandleViewBackgroundColor(getBackground().getColor());
    }

    private void setHandleViewBackgroundColor(int color) {
        if (mHandleDrawable == null) return;
        ((GradientDrawable) mHandleDrawable.mutate()).setColor(color);
    }

    @Override
    public boolean onLongClick(View v) {
        if (v == mCloseButton || v == mMinimizeButton || v.getParent() == mCustomActionButtons) {
            return Toast.showAnchoredToast(getContext(), v, v.getContentDescription());
        }
        return false;
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    static String parsePublisherNameFromUrl(GURL url) {
        // TODO(ianwen): Make it generic to parse url from URI path. http://crbug.com/599298
        // The url should look like: https://www.google.com/amp/s/www.nyt.com/ampthml/blogs.html
        // or https://www.google.com/amp/www.nyt.com/ampthml/blogs.html.
        String[] segments = url.getPath().split("/");
        if (segments.length >= 4 && "amp".equals(segments[1])) {
            if (segments[2].length() > 1) return segments[2];
            return segments[3];
        }
        return url.getSpec();
    }

    @Override
    protected void onMenuButtonDisabled() {
        super.onMenuButtonDisabled();
        // In addition to removing the menu button, we also need to remove the margin on the custom
        // action button.
        ViewGroup.MarginLayoutParams p =
                (ViewGroup.MarginLayoutParams) mCustomActionButtons.getLayoutParams();
        p.setMarginEnd(0);
        mCustomActionButtons.setLayoutParams(p);
    }

    @Override
    public CaptureReadinessResult isReadyForTextureCapture() {
        if (ToolbarFeatures.shouldSuppressCaptures()) {
            CustomTabCaptureStateToken currentToken = generateCaptureStateToken();
            final @ToolbarSnapshotDifference int difference =
                    currentToken.getAnyDifference(mLastCustomTabCaptureStateToken);
            if (difference == ToolbarSnapshotDifference.NONE) {
                return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SNAPSHOT_SAME);
            } else {
                return CaptureReadinessResult.readyWithSnapshotDifference(difference);
            }
        } else {
            return CaptureReadinessResult.unknown(/* isReady= */ true);
        }
    }

    @Override
    public void setTextureCaptureMode(boolean textureMode) {
        if (textureMode) {
            mLastCustomTabCaptureStateToken = generateCaptureStateToken();
        }
    }

    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        // Ignore when the changed view is our self. This happens on startup, and is not our
        // container being changed.
        if (changedView != this) {
            for (Callback<Integer> observer : mContainerVisibilityChangeObserverList) {
                observer.onResult(visibility);
            }
        }
    }

    @Override
    public void setToolbarColorObserver(@NonNull ToolbarColorObserver toolbarColorObserver) {
        super.setToolbarColorObserver(toolbarColorObserver);
        notifyToolbarColorChanged(getBackground().getColor());
    }

    /** Subscribe to container visibility changes. */
    public void addContainerVisibilityChangeObserver(Callback<Integer> observer) {
        mContainerVisibilityChangeObserverList.addObserver(observer);
    }

    /** Unsubscribe to container visibility changes. */
    public void removeContainerVisibilityChangeObserver(Callback<Integer> observer) {
        mContainerVisibilityChangeObserverList.removeObserver(observer);
    }

    private CustomTabCaptureStateToken generateCaptureStateToken() {
        // Must convert CharSequence to String in order for equality to be clearly defined.
        String url = mLocationBar.mUrlBar.getText().toString();
        String title = mLocationBar.mTitleBar.getText().toString();
        boolean minimizeVisible =
                mMinimizeButton != null && mMinimizeButton.getVisibility() == VISIBLE;
        var minimizeTag =
                mMinimizeButton != null ? mMinimizeButton.getTag(R.id.highlight_state) : null;
        boolean minimizeHighlighted = Boolean.TRUE.equals(minimizeTag);
        return new CustomTabCaptureStateToken(
                url,
                title,
                getBackground().getColor(),
                mLocationBar.mAnimDelegate.getSecurityIconRes(),
                mLocationBar.mAnimDelegate.isInAnimation(),
                getWidth(),
                minimizeVisible,
                minimizeHighlighted);
    }

    private static boolean shouldNestSecurityIcon() {
        return ChromeFeatureList.sCctNestedSecurityIcon.isEnabled();
    }

    /** Custom tab-specific implementation of the LocationBar interface. */
    @VisibleForTesting
    public class CustomTabLocationBar
            implements LocationBar,
                    UrlBar.UrlBarDelegate,
                    LocationBarDataProvider.Observer,
                    View.OnLongClickListener,
                    ToolbarBrandingDelegate,
                    CookieControlsObserver {
        private static final int TITLE_ANIM_DELAY_MS = 800;
        private static final int MIN_URL_BAR_VISIBLE_TIME_POST_BRANDING_MS = 3000;

        private static final int STATE_DOMAIN_ONLY = 0;
        private static final int STATE_TITLE_ONLY = 1;
        private static final int STATE_DOMAIN_AND_TITLE = 2;
        private static final int STATE_EMPTY = 3; // Not used as a regular state.
        private static final int COOKIE_CONTROLS_ICON_DISPLAY_TIMEOUT = 8500;
        private int mState = STATE_DOMAIN_ONLY;

        // Used for After branding runnables
        private static final int KEY_UPDATE_TITLE_POST_BRANDING = 0;
        private static final int KEY_UPDATE_URL_POST_BRANDING = 1;
        private static final int TOTAL_POST_BRANDING_KEYS = 2;

        private LocationBarDataProvider mLocationBarDataProvider;
        private Supplier<EphemeralTabCoordinator> mEphemeralTabCoordinatorSupplier;
        private Supplier<ModalDialogManager> mModalDialogManagerSupplier;
        private UrlBarCoordinator mUrlCoordinator;
        private TabCreator mTabCreator;

        private TextView mUrlBar;
        private TextView mTitleBar;
        private View mLocationBarFrameLayout;
        private View mTitleUrlContainer;
        private ImageButton mSecurityButton;

        private CustomTabToolbarAnimationDelegate mAnimDelegate;
        private final Runnable mTitleAnimationStarter =
                new Runnable() {
                    @Override
                    public void run() {
                        mAnimDelegate.startTitleAnimation(getContext());
                    }
                };

        private final Runnable[] mAfterBrandingRunnables = new Runnable[TOTAL_POST_BRANDING_KEYS];
        private final View.OnLayoutChangeListener mButtonsVisibilityUpdater =
                (v, l, t, r, b, ol, ot, or, ob) -> setButtonsVisibility();
        private boolean mCurrentlyShowingBranding;
        private boolean mBrandingStarted;
        private boolean mOmniboxEnabled;
        private Drawable mOmniboxBackground;
        private CallbackController mCallbackController = new CallbackController();
        // Cached the state before branding start so we can reset to the state when its done.
        private @Nullable Integer mPreBandingState;
        private PageInfoIPHController mPageInfoIPHController;
        private int mTouchTargetSize;
        private ToolbarBrandingOverlayCoordinator mBrandingOverlayCoordinator;

        public View getLayout() {
            return mLocationBarFrameLayout;
        }

        public ImageButton getSecurityButton() {
            return mSecurityButton;
        }

        public boolean isShowingTitleOnly() {
            return mState == STATE_TITLE_ONLY;
        }

        @Override
        public boolean unfocusUrlBarOnBackPressed() {
            return false;
        }

        @Override
        public void showBrandingLocationBar() {
            mBrandingStarted = true;

            if (ChromeFeatureList.sCctRevampedBranding.isEnabled()) {
                ViewStub stub = findViewById(R.id.branding_stub);

                if (stub != null) {
                    PropertyModel model =
                            new PropertyModel.Builder(ToolbarBrandingOverlayProperties.ALL_KEYS)
                                    .with(
                                            ToolbarBrandingOverlayProperties.COLOR_DATA,
                                            new ToolbarBrandingOverlayProperties.ColorData(
                                                    getBackground().getColor(),
                                                    mBrandedColorScheme))
                                    .build();
                    mBrandingOverlayCoordinator =
                            new ToolbarBrandingOverlayCoordinator(stub, model);

                    return;
                }
            }

            // Store the title and domain setting, if the empty state is not in used. Otherwise
            // regular state has already been stored.
            if (!mCurrentlyShowingBranding) {
                mCurrentlyShowingBranding = true;
                cacheRegularState();
            }

            // We use url bar to show the branding text and hide the title bar so the text will
            // align with the security icon.
            setUrlBarHiddenIgnoreBranding(false);
            setShowTitleIgnoreBranding(false);

            mAnimDelegate.setUseRotationSecurityButtonTransition(true);
            showBrandingIconAndText();
        }

        @Override
        public void showEmptyLocationBar() {
            mBrandingStarted = true;
            mCurrentlyShowingBranding = true;

            // Force setting the LocationBar element visibility, while cache their state.
            cacheRegularState();

            mState = STATE_EMPTY;
            mUrlBar.setVisibility(View.GONE);
            mTitleBar.setVisibility(View.GONE);
        }

        @Override
        public void showRegularToolbar() {
            mCurrentlyShowingBranding = false;

            if (ChromeFeatureList.sCctRevampedBranding.isEnabled()) {
                if (mBrandingOverlayCoordinator != null) {
                    mBrandingOverlayCoordinator.hideAndDestroy();
                }
            }

            recoverFromRegularState();
            runAfterBrandingRunnables();
            mAnimDelegate.setUseRotationSecurityButtonTransition(false);

            int token = mBrowserControlsVisibilityDelegate.showControlsPersistent();
            PostTask.postDelayedTask(
                    TaskTraits.UI_USER_VISIBLE,
                    () -> mBrowserControlsVisibilityDelegate.releasePersistentShowingToken(token),
                    MIN_URL_BAR_VISIBLE_TIME_POST_BRANDING_MS);
        }

        // CookieControlsObserver interface
        @Override
        public void onHighlightCookieControl(boolean shouldHighlight) {
            if (mShouldHighlightCookieControlsIcon) return;
            mShouldHighlightCookieControlsIcon = shouldHighlight;
        }

        @Override
        public void onStatusChanged(
                boolean controlsVisible,
                boolean protectionsOn,
                int enforcement,
                int blockingStatus,
                long expiration) {
            mCookieControlsVisible = controlsVisible;
            mThirdPartyCookiesBlocked = protectionsOn;
            mBlockingStatus3pcd = blockingStatus;
        }

        private void cacheRegularState() {
            String assertMsg =
                    "mPreBandingState already exists! mPreBandingState = " + mPreBandingState;
            assert mPreBandingState == null : assertMsg;

            mPreBandingState = mState;
        }

        private void recoverFromRegularState() {
            assert !mCurrentlyShowingBranding;
            assert mPreBandingState != null;

            boolean showTitle =
                    mPreBandingState == STATE_TITLE_ONLY
                            || mPreBandingState == STATE_DOMAIN_AND_TITLE;
            boolean hideUrl = mPreBandingState == STATE_TITLE_ONLY;
            mPreBandingState = null;

            setUrlBarHiddenIgnoreBranding(hideUrl);
            setShowTitleIgnoreBranding(showTitle);
        }

        public void onFinishInflate(View container) {
            mUrlBar = container.findViewById(R.id.url_bar);
            mUrlBar.setHint("");
            mUrlBar.setEnabled(false);
            mTitleBar = container.findViewById(R.id.title_bar);
            mLocationBarFrameLayout = container.findViewById(R.id.location_bar_frame_layout);
            mTitleUrlContainer = container.findViewById(R.id.title_url_container);
            mTitleUrlContainer.setOnLongClickListener(this);

            int securityButtonId =
                    shouldNestSecurityIcon() ? R.id.security_icon : R.id.security_button;
            mSecurityButton = container.findViewById(securityButtonId);
            mSecurityButton.setVisibility(INVISIBLE);

            // If the security icon is nested, only the url bar should be offset by it.
            View securityButtonOffsetTarget =
                    shouldNestSecurityIcon()
                            ? mTitleUrlContainer.findViewById(R.id.url_bar)
                            : mTitleUrlContainer;

            mAnimDelegate =
                    new CustomTabToolbarAnimationDelegate(
                            mSecurityButton,
                            securityButtonOffsetTarget,
                            this::adjustTitleUrlBarPadding,
                            R.dimen.location_bar_icon_width);
            addButtonsVisibilityUpdater();
        }

        private void removeButtonsVisibilityUpdater() {
            mTitleUrlContainer.removeOnLayoutChangeListener(mButtonsVisibilityUpdater);
        }

        private void addButtonsVisibilityUpdater() {
            if (mTitleUrlContainer != null) {
                mTitleUrlContainer.addOnLayoutChangeListener(mButtonsVisibilityUpdater);
            }
        }

        public void init(
                LocationBarDataProvider locationBarDataProvider,
                Supplier<ModalDialogManager> modalDialogManagerSupplier,
                Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
                TabCreator tabCreator,
                ActionMode.Callback actionModeCallback) {
            mLocationBarDataProvider = locationBarDataProvider;
            mEphemeralTabCoordinatorSupplier = ephemeralTabCoordinatorSupplier;
            mLocationBarDataProvider.addObserver(this);
            mModalDialogManagerSupplier = modalDialogManagerSupplier;
            mUrlCoordinator =
                    new UrlBarCoordinator(
                            getContext(),
                            (UrlBar) mUrlBar,
                            /* windowDelegate= */ null,
                            actionModeCallback,
                            /* focusChangeCallback= */ (unused) -> {},
                            this,
                            new NoOpkeyboardVisibilityDelegate(),
                            isIncognitoBranded());
            mTabCreator = tabCreator;
            mTouchTargetSize = getResources().getDimensionPixelSize(R.dimen.min_touch_target_size);
            updateColors();
            updateSecurityIcon();
            updateProgressBarColors();
            updateUrlBar();
        }

        public void setUrlBarHidden(boolean hideUrlBar) {
            if (mCurrentlyShowingBranding) {
                mAfterBrandingRunnables[KEY_UPDATE_URL_POST_BRANDING] =
                        () -> setUrlBarHiddenIgnoreBranding(hideUrlBar);
                return;
            }
            setUrlBarHiddenIgnoreBranding(hideUrlBar);
        }

        private void setUrlBarHiddenIgnoreBranding(boolean hideUrlBar) {
            // Urlbar visibility cannot be toggled if it is the only visible element.
            if (mState == STATE_DOMAIN_ONLY) return;

            if (hideUrlBar && mState == STATE_DOMAIN_AND_TITLE) {
                mState = STATE_TITLE_ONLY;
                mAnimDelegate.setTitleAnimationEnabled(false);
                mUrlBar.setVisibility(View.GONE);
                mTitleBar.setVisibility(View.VISIBLE);
                LayoutParams lp = (LayoutParams) mTitleBar.getLayoutParams();
                lp.bottomMargin = 0;
                mTitleBar.setLayoutParams(lp);
                mTitleBar.setTextSize(
                        TypedValue.COMPLEX_UNIT_PX,
                        getResources().getDimension(R.dimen.location_bar_url_text_size));
            } else if (!hideUrlBar && mState == STATE_TITLE_ONLY) {
                mState = STATE_DOMAIN_AND_TITLE;
                mTitleBar.setVisibility(View.VISIBLE);
                mUrlBar.setTextSize(
                        TypedValue.COMPLEX_UNIT_PX,
                        getResources().getDimension(R.dimen.custom_tabs_url_text_size));
                mUrlBar.setVisibility(View.VISIBLE);
                LayoutParams lp = (LayoutParams) mTitleBar.getLayoutParams();
                lp.bottomMargin =
                        getResources()
                                .getDimensionPixelSize(
                                        R.dimen.custom_tabs_toolbar_vertical_padding);
                mTitleBar.setLayoutParams(lp);
                mTitleBar.setTextSize(
                        TypedValue.COMPLEX_UNIT_PX,
                        getResources().getDimension(R.dimen.custom_tabs_title_text_size));
                // Refresh the status icon and url bar.
                updateUrlBar();
                mLocationBarModel.notifySecurityStateChanged();
            } else if (mState == STATE_EMPTY) {
                // If state is empty, that means Location bar is recovering from empty location bar
                // to whatever new state it is. We skip the state assertion and the end.
                if (!hideUrlBar) {
                    mState = STATE_DOMAIN_ONLY;
                    mUrlBar.setVisibility(View.VISIBLE);
                }
            } else {
                assert false : "Unreached state";
            }
        }

        public void onNativeLibraryReady() {
            mSecurityButton.setOnClickListener(v -> showPageInfo());
            if (!mOmniboxEnabled && shouldNestSecurityIcon()) {
                mTitleUrlContainer.setOnClickListener(v -> showPageInfo());
                // The title and url are independently focusable for accessibility. Set
                // AccessibilityNodeInfo on each to indicate they respond to clicks / long clicks
                // via the listeners set on mTitleUrlContainer.
                setTitleUrlBarAccessibilityDelegate(mTitleBar);
                setTitleUrlBarAccessibilityDelegate(mUrlBar);
            }
        }

        private void setTitleUrlBarAccessibilityDelegate(View view) {
            view.setAccessibilityDelegate(
                    new View.AccessibilityDelegate() {
                        @Override
                        public void onInitializeAccessibilityNodeInfo(
                                View host, AccessibilityNodeInfo info) {
                            super.onInitializeAccessibilityNodeInfo(host, info);
                            info.setLongClickable(true);
                            info.setClickable(true);
                            info.setEnabled(true);
                            info.setEditable(false);
                        }
                    });
        }

        private void showPageInfo() {
            Tab currentTab = mLocationBarDataProvider.getTab();
            if (currentTab == null) return;
            WebContents webContents = currentTab.getWebContents();
            if (webContents == null) return;
            Activity activity = currentTab.getWindowAndroid().getActivity().get();
            if (activity == null) return;
            if (mCurrentlyShowingBranding) return;
            // For now we don't show "store info" row for custom tab.
            new ChromePageInfo(
                            mModalDialogManagerSupplier,
                            TrustedCdn.getContentPublisher(getToolbarDataProvider().getTab()),
                            OpenedFromSource.TOOLBAR,
                            /* storeInfoActionHandlerSupplier= */ null,
                            mEphemeralTabCoordinatorSupplier,
                            mTabCreator)
                    .show(currentTab, ChromePageInfoHighlight.noHighlight());
        }

        @Override
        public View getViewForUrlBackFocus() {
            Tab tab = getCurrentTab();
            if (tab == null) return null;
            return tab.getView();
        }

        @Override
        public boolean allowKeyboardLearning() {
            return !CustomTabToolbar.this.isOffTheRecord();
        }

        @Override
        public void onFocusByTouch() {}

        @Override
        public void onTouchAfterFocus() {}

        // LocationBarDataProvider.Observer implementation
        // Using the default empty onIncognitoStateChanged.
        // Using the default empty onNtpStartedLoading.

        @Override
        public void onPrimaryColorChanged() {
            updateColors();
            updateSecurityIcon();
            updateProgressBarColors();
        }

        @Override
        public void onSecurityStateChanged() {
            updateSecurityIcon();
        }

        @Override
        public void onTitleChanged() {
            updateTitleBar();
        }

        @Override
        public void onUrlChanged() {
            updateUrlBar();
        }

        @Override
        public void onPageLoadStopped() {
            if (mPageInfoIPHController == null) {
                Tab currentTab = getCurrentTab();
                if (currentTab == null) return;
                Activity activity = currentTab.getWindowAndroid().getActivity().get();
                if (activity == null) return;
                mPageInfoIPHController =
                        new PageInfoIPHController(
                                new UserEducationHelper(
                                        activity,
                                        currentTab.getProfile(),
                                        new Handler(Looper.getMainLooper())),
                                getSecurityIconView());
            }
            if (mBlockingStatus3pcd != CookieBlocking3pcdStatus.NOT_IN3PCD) {
                if (!mCookieControlsVisible || !mThirdPartyCookiesBlocked) return;
                // TODO(b/332761678): Add reminder IPH here.
            } else if (mShouldHighlightCookieControlsIcon) {
                mPageInfoIPHController.showCookieControlsIPH(
                        COOKIE_CONTROLS_ICON_DISPLAY_TIMEOUT, R.string.cookie_controls_iph_message);
                animateCookieControlsIcon();
                mShouldHighlightCookieControlsIcon = false;
            }
        }

        @Override
        public void updateVisualsForState() {
            updateColorsForBackground(getBackground().getColor());
            updateSecurityIcon();
            updateProgressBarColors();
            updateUrlBar();
        }

        private void updateLeftMarginOfTitleUrlContainer() {
            // If the security icon is nested, we shouldn't move the whole title-url container since
            // the icon is part of the container now.
            if (shouldNestSecurityIcon()) return;

            int leftMargin = mSecurityButton.getMeasuredWidth();
            LayoutParams lp = (LayoutParams) mTitleUrlContainer.getLayoutParams();

            if (mSecurityButton.getVisibility() == View.GONE) {
                leftMargin -= mSecurityButton.getMeasuredWidth();
            }
            lp.leftMargin = leftMargin;
            mTitleUrlContainer.setLayoutParams(lp);
        }

        private void updateProgressBarColors() {
            final ToolbarProgressBar progressBar = getProgressBar();
            if (progressBar == null) return;
            final Context context = getContext();
            final int backgroundColor = getBackground().getColor();
            if (ThemeUtils.isUsingDefaultToolbarColor(
                    context, /* isIncognito= */ false, backgroundColor)) {
                progressBar.setBackgroundColor(
                        context.getColor(R.color.progress_bar_bg_color_list));
                progressBar.setForegroundColor(
                        SemanticColorUtils.getProgressBarForeground(context));
            } else {
                progressBar.setThemeColor(backgroundColor, /* isIncognito= */ false);
            }
        }

        private void showBrandingIconAndText() {
            ColorStateList colorStateList =
                    AppCompatResources.getColorStateList(
                            getContext(), mLocationBarDataProvider.getSecurityIconColorStateList());
            ImageViewCompat.setImageTintList(mSecurityButton, colorStateList);
            mAnimDelegate.updateSecurityButton(R.drawable.chromelogo16);

            mUrlCoordinator.setUrlBarData(
                    UrlBarData.forNonUrlText(
                            getContext().getString(R.string.twa_running_in_chrome)),
                    UrlBar.ScrollType.NO_SCROLL,
                    SelectionState.SELECT_ALL);
        }

        private void runAfterBrandingRunnables() {
            // Always refresh the security icon and URL bar when branding is finished.
            // If Title is changed during branding, it should already get addressed in
            // #setShowTitle.
            updateUrlBar();
            mLocationBarModel.notifySecurityStateChanged();

            for (int i = 0; i < mAfterBrandingRunnables.length; i++) {
                Runnable runnable = mAfterBrandingRunnables[i];
                if (runnable != null) {
                    runnable.run();
                    mAfterBrandingRunnables[i] = null;
                }
            }
        }

        private void updateSecurityIcon() {
            if (mState == STATE_TITLE_ONLY || mCurrentlyShowingBranding) return;

            int securityIconResource = 0;
            if (!shouldNestSecurityIcon() || !isSecureLevel()) {
                securityIconResource =
                        mLocationBarDataProvider.getSecurityIconResource(
                                DeviceFormFactor.isNonMultiDisplayContextOnTablet(getContext()));
            }
            if (securityIconResource != 0) {
                ColorStateList colorStateList =
                        AppCompatResources.getColorStateList(
                                getContext(),
                                mLocationBarDataProvider.getSecurityIconColorStateList());
                ImageViewCompat.setImageTintList(mSecurityButton, colorStateList);
            }
            mAnimDelegate.updateSecurityButton(securityIconResource);
            mSecurityIconResourceForTesting = securityIconResource;

            int contentDescriptionId =
                    mLocationBarDataProvider.getSecurityIconContentDescriptionResourceId();
            String contentDescription = getContext().getString(contentDescriptionId);
            mSecurityButton.setContentDescription(contentDescription);
        }

        /** Returns whether the current security level is considered secure. */
        private boolean isSecureLevel() {
            @ConnectionSecurityLevel
            int securityLevel = mLocationBarDataProvider.getSecurityLevel();
            return securityLevel == ConnectionSecurityLevel.SECURE
                    || securityLevel == ConnectionSecurityLevel.SECURE_WITH_POLICY_INSTALLED_CERT;
        }

        @VisibleForTesting(otherwise = VisibleForTesting.NONE)
        public int getSecurityIconResourceForTesting() {
            return mSecurityIconResourceForTesting;
        }

        private void animateCookieControlsIcon() {
            mTaskHandler.removeCallbacksAndMessages(null);
            mAnimDelegate.setUseRotationSecurityButtonTransition(true);
            mAnimDelegate.updateSecurityButton(R.drawable.ic_eye_crossed);

            Runnable finishIconAnimation =
                    () -> {
                        updateSecurityIcon();
                        mAnimDelegate.setUseRotationSecurityButtonTransition(false);
                    };
            mTaskHandler.postDelayed(finishIconAnimation, COOKIE_CONTROLS_ICON_DISPLAY_TIMEOUT);
        }

        private void updateTitleBar() {
            if (mCurrentlyShowingBranding) return;
            String title = mLocationBarDataProvider.getTitle();

            // If the url is about:blank, we shouldn't show a title as it is prone to spoofing.
            if (!mLocationBarDataProvider.hasTab()
                    || TextUtils.isEmpty(title)
                    || ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL.equals(getUrl().getSpec())) {
                mTitleBar.setText("");
                return;
            }

            // It takes some time to parse the title of the webcontent, and before that
            // LocationBarDataProvider#getTitle always returns the url. We postpone the title
            // animation until the title is authentic.
            if ((mState == STATE_DOMAIN_AND_TITLE || mState == STATE_TITLE_ONLY)
                    && !title.equals(mLocationBarDataProvider.getCurrentGurl().getSpec())
                    && !title.equals(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL)) {
                // Delay the title animation until security icon animation finishes.
                // If this is updated after branding, we don't need to wait.
                PostTask.postDelayedTask(
                        TaskTraits.UI_DEFAULT,
                        mTitleAnimationStarter,
                        mBrandingStarted ? 0 : TITLE_ANIM_DELAY_MS);
            }

            mTitleBar.setText(title);
        }

        private void adjustTitleUrlBarPadding() {
            // Title/URL container height should get bigger to meet GAR guideline. Distribute
            // the diff evenly as a padding of title/URL view to keep them staying where
            // they are, and only make the content-wrapping container get bigger accordingly.
            // TODO(jinsukkim): Make the animation work for further navigation like
            //     title/url -> url -> title/url 1) the url-only view should be centered,
            //     and 2) the animation for the transition to title/url should work as well.
            int padding = (mTouchTargetSize - mTitleUrlContainer.getHeight()) / 2;
            mTitleUrlContainer.setMinimumHeight(mTouchTargetSize);
            mUrlBar.setPadding(0, 0, 0, padding);
            mTitleBar.setPadding(0, padding, 0, 0);

            // When the security icon is nested, it will be in the same container as the Url Bar.
            // So, they should have the same bottom padding to keep it aligned.
            if (shouldNestSecurityIcon()) {
                mSecurityButton.setPaddingRelative(
                        mSecurityButton.getPaddingStart(),
                        mSecurityButton.getPaddingTop(),
                        mSecurityButton.getPaddingEnd(),
                        padding);
            }
        }

        private void updateUrlBar() {
            if (mCurrentlyShowingBranding) return;
            Tab tab = getCurrentTab();
            if (tab == null) {
                mUrlCoordinator.setUrlBarData(
                        UrlBarData.EMPTY, UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_ALL);
                return;
            }

            if (mState == STATE_TITLE_ONLY) {
                if (!TextUtils.isEmpty(mLocationBarDataProvider.getTitle())) {
                    updateTitleBar();
                }
            }

            GURL publisherUrl = TrustedCdn.getPublisherUrl(tab);
            GURL url = getUrl();
            final CharSequence displayText;
            final int originStart;
            final int originEnd;
            if (publisherUrl != null) {
                String plainDisplayText =
                        getContext()
                                .getString(
                                        R.string.custom_tab_amp_publisher_url,
                                        UrlUtilities.extractPublisherFromPublisherUrl(
                                                publisherUrl));
                SpannableString formattedDisplayText =
                        SpanApplier.applySpans(
                                plainDisplayText,
                                new SpanInfo("<pub>", "</pub>", ORIGIN_SPAN),
                                new SpanInfo(
                                        "<bg>",
                                        "</bg>",
                                        new ForegroundColorSpan(mTint.getDefaultColor())));
                originStart = formattedDisplayText.getSpanStart(ORIGIN_SPAN);
                originEnd = formattedDisplayText.getSpanEnd(ORIGIN_SPAN);
                formattedDisplayText.removeSpan(ORIGIN_SPAN);
                displayText = formattedDisplayText;
            } else {
                UrlBarData urlBarData = mLocationBarDataProvider.getUrlBarData();
                originStart = 0;
                if (mOmniboxEnabled) {
                    displayText = urlBarData.displayText;
                    originEnd = 0;
                } else if (urlBarData.displayText != null) {
                    displayText =
                            urlBarData.displayText.subSequence(
                                    urlBarData.originStartIndex, urlBarData.originEndIndex);
                    originEnd = displayText.length();
                } else {
                    displayText = null;
                    originEnd = 0;
                }
            }

            mUrlCoordinator.setUrlBarData(
                    UrlBarData.create(url, displayText, originStart, originEnd, url.getSpec()),
                    UrlBar.ScrollType.SCROLL_TO_TLD,
                    SelectionState.SELECT_ALL);

            WebContents webContents = tab.getWebContents();
            if (webContents != null) {
                BrowserContextHandle originalBrowserContext =
                        tab.isOffTheRecord()
                                ? Profile.fromWebContents(webContents).getOriginalProfile()
                                : null;
                if (mCookieControlsBridge != null) {
                    mCookieControlsBridge.updateWebContents(webContents, originalBrowserContext);
                } else {
                    mCookieControlsBridge =
                            new CookieControlsBridge(this, webContents, originalBrowserContext);
                }
            }
        }

        private GURL getUrl() {
            Tab tab = getCurrentTab();
            if (tab == null) return GURL.emptyGURL();

            GURL publisherUrl = TrustedCdn.getPublisherUrl(tab);
            return publisherUrl != null ? publisherUrl : tab.getUrl();
        }

        private void updateColors() {
            updateOmniboxBackground();
            updateButtonsTint();

            if (mUrlCoordinator.setBrandedColorScheme(mBrandedColorScheme)) {
                // Update the URL to make it use the new color scheme.
                updateUrlBar();
            }

            mTitleBar.setTextColor(
                    OmniboxResourceProvider.getUrlBarPrimaryTextColor(
                            getContext(), mBrandedColorScheme));
        }

        private void updateOmniboxBackground() {
            if (mOmniboxBackground == null) return;
            @ColorInt int background = getBackground().getColor();
            @ColorInt
            int bg =
                    ThemeUtils.getTextBoxColorForToolbarBackgroundInNonNativePage(
                            getContext(),
                            background,
                            mBrandedColorScheme == BrandedColorScheme.INCOGNITO,
                            /* isCustomTab= */ true);
            mOmniboxBackground.setTint(bg);
        }

        @Override
        public void setShowTitle(boolean showTitle) {
            if (mCurrentlyShowingBranding) {
                mAfterBrandingRunnables[KEY_UPDATE_TITLE_POST_BRANDING] =
                        () -> setShowTitleIgnoreBranding(showTitle);
                return;
            }
            setShowTitleIgnoreBranding(showTitle);
        }

        private void setShowTitleIgnoreBranding(boolean showTitle) {
            if (showTitle) {
                if (mState == STATE_EMPTY) {
                    mState = STATE_TITLE_ONLY;
                } else {
                    mState = STATE_DOMAIN_AND_TITLE;
                }
                mAnimDelegate.prepareTitleAnim(mUrlBar, mTitleBar);
                setUrlBarVisuals(Gravity.BOTTOM, 0, R.dimen.custom_tabs_url_text_size);
            } else {
                mState = STATE_DOMAIN_ONLY;
                mTitleBar.setVisibility(View.GONE);

                // URL bar height should be as big as the touch target size when shown alone.
                // Update its minHeight and center it vertically.
                setUrlBarVisuals(
                        Gravity.CENTER_VERTICAL,
                        mTouchTargetSize,
                        R.dimen.custom_tabs_title_text_size);
            }
            mLocationBarModel.notifyTitleChanged();
        }

        private void setUrlBarVisuals(int gravity, int minHeight, int sizeId) {
            var params = (LinearLayout.LayoutParams) mUrlBar.getLayoutParams();
            params.gravity = gravity;
            mUrlBar.setLayoutParams(params);
            mUrlBar.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(sizeId));
            mUrlBar.setMinimumHeight(minHeight);
            mTitleUrlContainer.setMinimumHeight(0);
        }

        @Override
        public View getContainerView() {
            return CustomTabToolbar.this;
        }

        @Override
        public View getSecurityIconView() {
            return mSecurityButton;
        }

        @Override
        public void backKeyPressed() {
            assert false : "The URL bar should never take focus in CCTs.";
        }

        @Override
        public void destroy() {
            if (mTaskHandler != null) {
                mTaskHandler.removeCallbacksAndMessages(null);
            }
            if (mCallbackController != null) {
                mCallbackController.destroy();
                mCallbackController = null;
            }
            if (mLocationBarDataProvider != null) {
                mLocationBarDataProvider.removeObserver(this);
                mLocationBarDataProvider = null;
            }
            if (mBrandingOverlayCoordinator != null) {
                mBrandingOverlayCoordinator.destroy();
                mBrandingOverlayCoordinator = null;
            }
        }

        @Override
        public void showUrlBarCursorWithoutFocusAnimations() {}

        @Override
        public void clearUrlBarCursorWithoutFocusAnimations() {}

        @Override
        public void selectAll() {}

        @Override
        public void revertChanges() {}

        @Nullable
        @Override
        public OmniboxStub getOmniboxStub() {
            return null;
        }

        @Override
        public UrlBarData getUrlBarData() {
            return mUrlCoordinator.getUrlBarData();
        }

        @Override
        public Optional<OmniboxSuggestionsVisualState> getOmniboxSuggestionsVisualState() {
            return Optional.empty();
        }

        @Override
        public boolean onLongClick(View v) {
            if (v == mTitleUrlContainer) {
                Tab tab = getCurrentTab();
                if (tab == null) return false;
                Clipboard.getInstance().copyUrlToClipboard(tab.getOriginalUrl());
                return true;
            }
            return false;
        }

        void setAnimDelegateForTesting(CustomTabToolbarAnimationDelegate animDelegate) {
            mAnimDelegate = animDelegate;
        }

        void setTitleUrlContainerForTesting(View titleUrlContainer) {
            mTitleUrlContainer = titleUrlContainer;
        }

        void setIPHControllerForTesting(PageInfoIPHController pageInfoIPHController) {
            mPageInfoIPHController = pageInfoIPHController;
        }

        void setOmniboxEnabled(String clientPackageName, @Nullable Consumer<Tab> tapHandler) {
            mOmniboxEnabled = true;
            mOmniboxBackground =
                    AppCompatResources.getDrawable(
                            getContext(), R.drawable.custom_tabs_url_bar_omnibox_bg);
            mOmniboxBackground.mutate();
            mOmniboxBackground.setTint(
                    ChromeColors.getSurfaceColor(getContext(), R.dimen.toolbar_text_box_elevation));
            mLocationBarFrameLayout.setBackground(mOmniboxBackground);
            var lp = mLocationBarFrameLayout.getLayoutParams();
            lp.height =
                    getResources()
                            .getDimensionPixelSize(R.dimen.custom_tabs_location_bar_active_height);
            mLocationBarFrameLayout.setLayoutParams(lp);

            lp = mUrlBar.getLayoutParams();
            lp.height =
                    getResources().getDimensionPixelSize(R.dimen.custom_tabs_url_bar_active_height);
            mUrlBar.setLayoutParams(lp);

            View urlBarWrapper = findViewById(R.id.url_bar_wrapper);
            FrameLayout.LayoutParams locationBarLayoutParams =
                    (FrameLayout.LayoutParams) urlBarWrapper.getLayoutParams();
            locationBarLayoutParams.gravity = Gravity.CENTER_VERTICAL;
            urlBarWrapper.setLayoutParams(locationBarLayoutParams);

            mTitleUrlContainer.setPadding(
                    mTitleUrlContainer.getPaddingLeft(),
                    mTitleUrlContainer.getPaddingTop(),
                    getResources().getDimensionPixelSize(R.dimen.toolbar_edge_padding),
                    mTitleUrlContainer.getPaddingBottom());

            mTitleUrlContainer.setOnClickListener(
                    v -> {
                        RecordUserAction.record("CustomTabs.OmniboxClicked");
                        var tab = getCurrentTab();
                        if (tapHandler != null) {
                            tapHandler.accept(tab);
                        } else {
                            SearchActivityClientImpl.requestOmniboxForResult(
                                    tab.getWindowAndroid().getActivity().get(),
                                    tab.getUrl(),
                                    clientPackageName);
                        }
                    });

            mUrlBar.setAccessibilityDelegate(
                    new View.AccessibilityDelegate() {
                        @Override
                        public void onInitializeAccessibilityNodeInfo(
                                View host, AccessibilityNodeInfo info) {
                            super.onInitializeAccessibilityNodeInfo(host, info);
                            info.setClickable(true);
                            info.setLongClickable(true);
                            info.setEnabled(true);
                            info.setEditable(false);
                        }
                    });
        }
    }

    boolean isMaximizeButtonEnabledForTesting() {
        return mMaximizeButtonEnabled;
    }
}