chromium/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.omnibox;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.view.ActionMode;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;

import org.chromium.base.Callback;
import org.chromium.base.CallbackController;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.lens.LensController;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.merchant_viewer.MerchantTrustSignalsCoordinator;
import org.chromium.chrome.browser.omnibox.LocationBarMediator.OmniboxUma;
import org.chromium.chrome.browser.omnibox.LocationBarMediator.SaveOfflineButtonState;
import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
import org.chromium.chrome.browser.omnibox.status.StatusCoordinator.PageInfoAction;
import org.chromium.chrome.browser.omnibox.status.StatusView;
import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxLoadUrlParams;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdownScrollListener;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsVisualState;
import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor.BookmarkState;
import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabWindowManager;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
import org.chromium.components.omnibox.AutocompleteMatch;
import org.chromium.components.omnibox.action.OmniboxActionDelegate;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.base.WindowDelegate;
import org.chromium.ui.modaldialog.ModalDialogManager;

import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier;

/**
 * The public API of the location bar component. Location bar responsibilities are:
 *
 * <ul>
 *   <li>Display the current URL.
 *   <li>Display Status.
 *   <li>Handle omnibox input.
 * </ul>
 *
 * <p>The coordinator creates and owns elements within this component.
 */
public class LocationBarCoordinator
        implements LocationBar, NativeInitObserver, AutocompleteDelegate {
    private OmniboxSuggestionsDropdownEmbedderImpl mOmniboxDropdownEmbedderImpl;

    /** Identifies coordinators with methods specific to a device type. */
    public interface SubCoordinator {
        /** Destroys SubCoordinator. */
        void destroy();
    }

    private LocationBarLayout mLocationBarLayout;
    @Nullable private SubCoordinator mSubCoordinator;
    private ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
    private UrlBarCoordinator mUrlCoordinator;
    private AutocompleteCoordinator mAutocompleteCoordinator;
    private StatusCoordinator mStatusCoordinator;
    private WindowDelegate mWindowDelegate;
    private WindowAndroid mWindowAndroid;
    private LocationBarMediator mLocationBarMediator;
    private View mUrlBar;
    private View mDeleteButton;
    private View mMicButton;
    private View mLensButton;
    private CallbackController mCallbackController = new CallbackController();
    private boolean mDestroyed;

    private boolean mNativeInitialized;
    private final @ColorInt int mDropdownStandardBackgroundColor;
    private final @ColorInt int mDropdownIncognitoBackgroundColor;
    private final @ColorInt int mSuggestionStandardBackgroundColor;
    private final @ColorInt int mSuggestionIncognitoBackgroundColor;

    /**
     * Creates {@link LocationBarCoordinator} and its subcoordinator: {@link
     * LocationBarCoordinatorPhone} or {@link LocationBarCoordinatorTablet}, depending on the type
     * of {@code locationBarLayout}; no sub-coordinator is created for other LocationBarLayout
     * subclasses. {@code LocationBarCoordinator} owns the subcoordinator. Destroying the former
     * destroys the latter.
     *
     * @param locationBarLayout Inflated {@link LocationBarLayout}. {@code LocationBarCoordinator}
     *     takes ownership and will destroy this object.
     * @param profileObservableSupplier The supplier of the active profile.
     * @param privacyPreferencesManager Privacy preference settings manager.
     * @param locationBarDataProvider {@link LocationBarDataProvider} to be used for accessing
     *     Toolbar state.
     * @param actionModeCallback The default callback for text editing action bar to use.
     * @param windowDelegate {@link WindowDelegate} that will provide {@link Window} related info.
     * @param windowAndroid {@link WindowAndroid} that is used by the owning {@link Activity}.
     * @param activityTabSupplier A Supplier to access the activity's current tab.
     * @param modalDialogManagerSupplier A supplier for {@link ModalDialogManager} object.
     * @param shareDelegateSupplier A supplier for {@link ShareDelegate} object.
     * @param incognitoStateProvider An {@link IncognitoStateProvider} to access the current
     *     incognito state.
     * @param activityLifecycleDispatcher Allows observation of the activity state.
     * @param overrideUrlLoadingDelegate Delegate that allows customization of url loading behavior.
     * @param backKeyBehavior Delegate that allows customization of back key behavior.
     * @param pageInfoAction Displays page info popup.
     * @param bringTabToFrontCallback Callback to bring the browser foreground and switch to a tab.
     * @param saveOfflineButtonState Whether the 'save offline' button should be enabled.
     * @param omniboxUma Interface for logging UMA histogram.
     * @param tabWindowManagerSupplier Supplier of glue-level TabWindowManager object.
     * @param bookmarkState State of a URL bookmark state.
     * @param isToolbarMicEnabledSupplier Whether toolbar mic is enabled or not.
     * @param merchantTrustSignalsCoordinatorSupplier Supplier of {@link
     *     MerchantTrustSignalsCoordinator}. Can be null if a store icon shouldn't be shown, such as
     *     when called from a search activity.
     * @param backPressManager The {@link BackPressManager} for intercepting back press.
     * @param tabModelSelectorSupplier Supplier of the {@link TabModelSelector}.
     * @param uiOverrides embedder-specific UI overrides
     * @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 LocationBarCoordinator(
            View locationBarLayout,
            View autocompleteAnchorView,
            ObservableSupplier<Profile> profileObservableSupplier,
            PrivacyPreferencesManager privacyPreferencesManager,
            LocationBarDataProvider locationBarDataProvider,
            ActionMode.Callback actionModeCallback,
            WindowDelegate windowDelegate,
            WindowAndroid windowAndroid,
            @NonNull Supplier<Tab> activityTabSupplier,
            Supplier<ModalDialogManager> modalDialogManagerSupplier,
            Supplier<ShareDelegate> shareDelegateSupplier,
            IncognitoStateProvider incognitoStateProvider,
            ActivityLifecycleDispatcher activityLifecycleDispatcher,
            OverrideUrlLoadingDelegate overrideUrlLoadingDelegate,
            BackKeyBehaviorDelegate backKeyBehavior,
            @NonNull PageInfoAction pageInfoAction,
            @NonNull Callback<Tab> bringTabToFrontCallback,
            @NonNull SaveOfflineButtonState saveOfflineButtonState,
            @NonNull OmniboxUma omniboxUma,
            @NonNull Supplier<TabWindowManager> tabWindowManagerSupplier,
            @NonNull BookmarkState bookmarkState,
            @NonNull BooleanSupplier isToolbarMicEnabledSupplier,
            @Nullable
                    Supplier<MerchantTrustSignalsCoordinator>
                            merchantTrustSignalsCoordinatorSupplier,
            @NonNull OmniboxActionDelegate omniboxActionDelegate,
            BrowserStateBrowserControlsVisibilityDelegate browserControlsVisibilityDelegate,
            @Nullable BackPressManager backPressManager,
            @Nullable
                    OmniboxSuggestionsDropdownScrollListener
                            omniboxSuggestionsDropdownScrollListener,
            @Nullable ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
            LocationBarEmbedderUiOverrides uiOverrides,
            @Nullable View baseChromeLayout) {
        mLocationBarLayout = (LocationBarLayout) locationBarLayout;
        mWindowDelegate = windowDelegate;
        mWindowAndroid = windowAndroid;
        mActivityLifecycleDispatcher = activityLifecycleDispatcher;
        mActivityLifecycleDispatcher.register(this);
        Context context = mLocationBarLayout.getContext();
        OneshotSupplierImpl<TemplateUrlService> templateUrlServiceSupplier =
                new OneshotSupplierImpl<>();
        DeferredIMEWindowInsetApplicationCallback deferredIMEWindowInsetApplicationCallback =
                new DeferredIMEWindowInsetApplicationCallback(
                        () -> mOmniboxDropdownEmbedderImpl.recalculateOmniboxAlignment());
        mOmniboxDropdownEmbedderImpl =
                new OmniboxSuggestionsDropdownEmbedderImpl(
                        mWindowAndroid,
                        autocompleteAnchorView,
                        mLocationBarLayout,
                        uiOverrides.isForcedPhoneStyleOmnibox(),
                        baseChromeLayout,
                        deferredIMEWindowInsetApplicationCallback::getCurrentKeyboardHeight);

        mUrlBar = mLocationBarLayout.findViewById(R.id.url_bar);
        // TODO(crbug.com/40733049): Inject LocaleManager instance to LocationBarCoordinator instead
        // of using the singleton.
        mLocationBarMediator =
                new LocationBarMediator(
                        context,
                        mLocationBarLayout,
                        locationBarDataProvider,
                        uiOverrides,
                        profileObservableSupplier,
                        privacyPreferencesManager,
                        overrideUrlLoadingDelegate,
                        LocaleManager.getInstance(),
                        templateUrlServiceSupplier,
                        backKeyBehavior,
                        windowAndroid,
                        isTabletWindow() && isTabletLayout(),
                        LensController.getInstance(),
                        saveOfflineButtonState,
                        omniboxUma,
                        isToolbarMicEnabledSupplier,
                        mOmniboxDropdownEmbedderImpl,
                        tabModelSelectorSupplier);
        if (backPressManager != null) {
            backPressManager.addHandler(mLocationBarMediator, BackPressHandler.Type.LOCATION_BAR);
        }
        mActivityLifecycleDispatcher.register(mLocationBarMediator);
        final boolean isIncognito =
                incognitoStateProvider != null && incognitoStateProvider.isIncognitoSelected();
        mUrlCoordinator =
                new UrlBarCoordinator(
                        context,
                        (UrlBar) mUrlBar,
                        windowDelegate,
                        actionModeCallback,
                        mCallbackController.makeCancelable(mLocationBarMediator::onUrlFocusChange),
                        mLocationBarMediator,
                        windowAndroid.getKeyboardDelegate(),
                        isIncognito);
        mAutocompleteCoordinator =
                new AutocompleteCoordinator(
                        mLocationBarLayout,
                        this,
                        mOmniboxDropdownEmbedderImpl,
                        mUrlCoordinator,
                        modalDialogManagerSupplier,
                        activityTabSupplier,
                        shareDelegateSupplier,
                        locationBarDataProvider,
                        profileObservableSupplier,
                        bringTabToFrontCallback,
                        tabWindowManagerSupplier,
                        bookmarkState,
                        omniboxActionDelegate,
                        omniboxSuggestionsDropdownScrollListener,
                        mActivityLifecycleDispatcher,
                        uiOverrides.isForcedPhoneStyleOmnibox(),
                        windowAndroid,
                        deferredIMEWindowInsetApplicationCallback);
        StatusView statusView = mLocationBarLayout.findViewById(R.id.location_bar_status);
        mStatusCoordinator =
                new StatusCoordinator(
                        isTabletWindow(),
                        statusView,
                        mUrlCoordinator,
                        locationBarDataProvider,
                        templateUrlServiceSupplier,
                        profileObservableSupplier,
                        windowAndroid,
                        pageInfoAction,
                        merchantTrustSignalsCoordinatorSupplier,
                        browserControlsVisibilityDelegate);
        mLocationBarMediator.setCoordinators(
                mUrlCoordinator, mAutocompleteCoordinator, mStatusCoordinator);

        mLocationBarMediator.addUrlFocusChangeListener(mAutocompleteCoordinator);
        mLocationBarMediator.addUrlFocusChangeListener(mUrlCoordinator);

        mDeleteButton = mLocationBarLayout.findViewById(R.id.delete_button);
        mDeleteButton.setOnClickListener(mLocationBarMediator::deleteButtonClicked);

        mMicButton = mLocationBarLayout.findViewById(R.id.mic_button);
        mMicButton.setOnClickListener(mLocationBarMediator::micButtonClicked);

        mLensButton = mLocationBarLayout.findViewById(R.id.lens_camera_button);
        mLensButton.setOnClickListener(mLocationBarMediator::lensButtonClicked);

        mUrlCoordinator.setTextChangeListener(mAutocompleteCoordinator::onTextChanged);
        mUrlCoordinator.setKeyDownListener(mLocationBarMediator);
        mUrlCoordinator.setTypingStartedListener(
                mLocationBarMediator::completeUrlFocusAnimationAndEnableSuggestions);

        // The LocationBar's direction is tied to the UrlBar's text direction. Icons inside the
        // location bar, e.g. lock, refresh, X, should be reversed if UrlBar's text is RTL.
        mUrlCoordinator.setUrlDirectionListener(
                mCallbackController.makeCancelable(
                        layoutDirection -> {
                            ViewCompat.setLayoutDirection(mLocationBarLayout, layoutDirection);
                            mAutocompleteCoordinator.updateSuggestionListLayoutDirection();
                        }));

        context.registerComponentCallbacks(mLocationBarMediator);
        mLocationBarLayout.initialize(
                mAutocompleteCoordinator,
                mUrlCoordinator,
                mStatusCoordinator,
                locationBarDataProvider);

        mDropdownStandardBackgroundColor =
                ChromeColors.getSurfaceColor(
                        context, R.dimen.omnibox_suggestion_dropdown_bg_elevation);
        mDropdownIncognitoBackgroundColor = context.getColor(R.color.omnibox_dropdown_bg_incognito);
        mSuggestionStandardBackgroundColor =
                OmniboxResourceProvider.getStandardSuggestionBackgroundColor(context);
        mSuggestionIncognitoBackgroundColor =
                context.getColor(R.color.omnibox_suggestion_bg_incognito);

        Callback<Profile> profileObserver =
                new Callback<>() {
                    @Override
                    public void onResult(Profile profile) {
                        templateUrlServiceSupplier.set(
                                TemplateUrlServiceFactory.getForProfile(profile));
                        profileObservableSupplier.removeObserver(this);
                    }
                };
        profileObservableSupplier.addObserver(profileObserver);

        if (isPhoneLayout()) {
            mSubCoordinator =
                    new LocationBarCoordinatorPhone(
                            (LocationBarPhone) locationBarLayout, mStatusCoordinator);
        } else if (isTabletLayout()) {
            mSubCoordinator =
                    new LocationBarCoordinatorTablet((LocationBarTablet) locationBarLayout);
        }
        // There is a third possibility: SearchActivityLocationBarLayout extends LocationBarLayout
        // and can be instantiated on phones *or* tablets.
    }

    @Override
    public void destroy() {
        if (mSubCoordinator != null) {
            mSubCoordinator.destroy();
            mSubCoordinator = null;
        }

        mUrlBar.setOnKeyListener(null);
        mUrlBar = null;

        mDeleteButton.setOnClickListener(null);
        mDeleteButton = null;

        mMicButton.setOnClickListener(null);
        mMicButton = null;

        mLensButton.setOnClickListener(null);
        mLensButton = null;

        mLocationBarMediator.removeUrlFocusChangeListener(mUrlCoordinator);
        mUrlCoordinator.destroy();
        mUrlCoordinator = null;

        mLocationBarLayout.getContext().unregisterComponentCallbacks(mLocationBarMediator);

        mLocationBarMediator.removeUrlFocusChangeListener(mAutocompleteCoordinator);
        mAutocompleteCoordinator.destroy();
        mAutocompleteCoordinator = null;

        mStatusCoordinator.destroy();
        mStatusCoordinator = null;

        mLocationBarLayout.destroy();
        mLocationBarLayout = null;

        mCallbackController.destroy();
        mCallbackController = null;

        mLocationBarMediator.destroy();
        mLocationBarMediator = null;
        GeolocationHeader.stopListeningForLocationUpdates();

        mDestroyed = true;
    }

    @Override
    public void onFinishNativeInitialization() {
        mActivityLifecycleDispatcher.unregister(this);
        mActivityLifecycleDispatcher = null;

        mLocationBarMediator.onFinishNativeInitialization();
        mUrlCoordinator.onFinishNativeInitialization();
        mAutocompleteCoordinator.onNativeInitialized();
        mStatusCoordinator.onNativeInitialized();
        mNativeInitialized = true;
    }

    /**
     * Runs logic that can't be invoked until after native is initialized but shouldn't be on the
     * critical path, e.g. pre-fetching autocomplete suggestions. Contrast with {@link
     * #onFinishNativeInitialization}, which is for logic that should be on the critical path and
     * need native to be initialized. This method must be called after onFinishNativeInitialization.
     */
    @Override
    public void onDeferredStartup() {
        assert mNativeInitialized;
        startAutocompletePrefetch();
    }

    @Override
    public void updateVisualsForState() {
        mLocationBarMediator.updateVisualsForState();
    }

    @Override
    public void setShowTitle(boolean showTitle) {
        mLocationBarMediator.setShowTitle(showTitle);
    }

    @Override
    public void requestUrlBarAccessibilityFocus() {
        mUrlCoordinator.requestAccessibilityFocus();
    }

    @Override
    public void showUrlBarCursorWithoutFocusAnimations() {
        mLocationBarMediator.showUrlBarCursorWithoutFocusAnimations();
    }

    @Override
    public void clearUrlBarCursorWithoutFocusAnimations() {
        mLocationBarMediator.clearUrlBarCursorWithoutFocusAnimations();
    }

    @Override
    public boolean unfocusUrlBarOnBackPressed() {
        if (mLocationBarMediator.isUrlBarFocused()) {
            mLocationBarMediator.backKeyPressed();
            return true;
        }
        return false;
    }

    @Override
    public void selectAll() {
        mUrlCoordinator.selectAll();
    }

    @Override
    public void revertChanges() {
        mLocationBarMediator.revertChanges();
    }

    @Override
    public View getContainerView() {
        return mLocationBarLayout;
    }

    @Override
    public View getSecurityIconView() {
        return mLocationBarLayout.getSecurityIconView();
    }

    /** Returns the {@link VoiceRecognitionHandler} associated with this LocationBar. */
    @Nullable
    @Override
    public VoiceRecognitionHandler getVoiceRecognitionHandler() {
        return mLocationBarMediator.getVoiceRecognitionHandler();
    }

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

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

    @Override
    public void addOmniboxSuggestionsDropdownScrollListener(
            OmniboxSuggestionsDropdownScrollListener listener) {
        mAutocompleteCoordinator.addOmniboxSuggestionsDropdownScrollListener(listener);
    }

    @Override
    public void removeOmniboxSuggestionsDropdownScrollListener(
            OmniboxSuggestionsDropdownScrollListener listener) {
        mAutocompleteCoordinator.removeOmniboxSuggestionsDropdownScrollListener(listener);
    }

    // AutocompleteDelegate implementation.
    @Override
    public void onUrlTextChanged() {
        mLocationBarMediator.onUrlTextChanged();
    }

    @Override
    public void onSuggestionsChanged(@Nullable AutocompleteMatch defaultMatch) {
        assert defaultMatch == null || defaultMatch.allowedToBeDefaultMatch();
        mLocationBarMediator.onSuggestionsChanged(defaultMatch);
    }

    @Override
    public void setKeyboardVisibility(boolean shouldShow, boolean delayHide) {
        mUrlCoordinator.setKeyboardVisibility(shouldShow, delayHide);
    }

    @Override
    public boolean isKeyboardActive() {
        return KeyboardVisibilityDelegate.getInstance()
                        .isKeyboardShowing(mLocationBarLayout.getContext(), mUrlBar)
                || (mLocationBarLayout.getContext().getResources().getConfiguration().keyboard
                        == Configuration.KEYBOARD_QWERTY);
    }

    @Override
    public void loadUrl(OmniboxLoadUrlParams omniboxLoadUrlParams) {
        mLocationBarMediator.loadUrl(omniboxLoadUrlParams);
    }

    @Override
    public boolean didFocusUrlFromFakebox() {
        return mLocationBarMediator.didFocusUrlFromFakebox();
    }

    @Override
    public boolean isUrlBarFocused() {
        return mLocationBarMediator.isUrlBarFocused();
    }

    @Override
    public void clearOmniboxFocus() {
        mLocationBarMediator.clearOmniboxFocus();
    }

    @Override
    public void setOmniboxEditingText(String text) {
        mUrlCoordinator.setUrlBarData(
                UrlBarData.forNonUrlText(text),
                UrlBar.ScrollType.NO_SCROLL,
                UrlBarCoordinator.SelectionState.SELECT_END);
        updateButtonVisibility();
    }

    /**
     * @see UrlBarCoordinator#getVisibleTextPrefixHint()
     */
    public CharSequence getOmniboxVisibleTextPrefixHint() {
        return mUrlCoordinator.getVisibleTextPrefixHint();
    }

    /**
     * @see UrlBarCoordinator#getTextWithoutAutocomplete()
     */
    public String getUrlBarTextWithoutAutocomplete() {
        return mUrlCoordinator.getTextWithoutAutocomplete();
    }

    /**
     * Returns the {@link LocationBarCoordinatorPhone} for this coordinator.
     *
     * @throws ClassCastException if this coordinator holds a {@link SubCoordinator} of a different
     *     type.
     */
    public @NonNull LocationBarCoordinatorPhone getPhoneCoordinator() {
        assert mSubCoordinator != null;
        return (LocationBarCoordinatorPhone) mSubCoordinator;
    }

    /**
     * Returns the {@link LocationBarCoordinatorTablet} for this coordinator.
     *
     * @throws ClassCastException if this coordinator holds a {@link SubCoordinator} of a different
     *     type.
     */
    public @NonNull LocationBarCoordinatorTablet getTabletCoordinator() {
        assert mSubCoordinator != null;
        return (LocationBarCoordinatorTablet) mSubCoordinator;
    }

    public boolean isDestroyed() {
        return mDestroyed;
    }

    /** Initiates a pre-fetch of autocomplete suggestions. */
    public void startAutocompletePrefetch() {
        if (!mNativeInitialized) return;
        mAutocompleteCoordinator.prefetchZeroSuggestResults();
    }

    /**
     * Updates progress of current the URL focus change animation.
     *
     * @param ntpSearchBoxScrollFraction The degree to which the omnibox has expanded to full width
     *     in NTP due to the NTP search box is being scrolled up.
     * @param urlFocusChangeFraction The degree to which the omnibox has expanded due to it is
     *     getting focused.
     */
    public void setUrlFocusChangeFraction(
            float ntpSearchBoxScrollFraction, float urlFocusChangeFraction) {
        mLocationBarMediator.setUrlFocusChangeFraction(
                ntpSearchBoxScrollFraction, urlFocusChangeFraction);
    }

    /**
     * Called to set the width of the location bar when the url bar is not focused.
     *
     * <p>Immediately after the animation to transition the URL bar from focused to unfocused
     * finishes, the layout width returned from #getMeasuredWidth() can differ from the final
     * unfocused width (e.g. this value) until the next layout pass is complete.
     *
     * <p>This value may be used to determine whether optional child views should be visible in the
     * unfocused location bar.
     *
     * @param unfocusedWidth The unfocused location bar width.
     */
    public void setUnfocusedWidth(int unfocusedWidth) {
        mLocationBarMediator.setUnfocusedWidth(unfocusedWidth);
    }

    /** Returns the {@link StatusCoordinator} for the LocationBar. */
    public StatusCoordinator getStatusCoordinator() {
        return mStatusCoordinator;
    }

    /**
     * @param focusable Whether the url bar should be focusable.
     */
    public void setUrlBarFocusable(boolean focusable) {
        mUrlCoordinator.setAllowFocus(focusable);
    }

    /**
     * Triggers a url focus change to begin or end, depending on the value of inProgress.
     *
     * @param inProgress Whether a focus change is in progress.
     */
    public void setUrlFocusChangeInProgress(boolean inProgress) {
        mLocationBarMediator.setUrlFocusChangeInProgress(inProgress);
    }

    /**
     * Handles any actions to be performed after all other actions triggered by the URL focus
     * change. This will be called after any animations are performed to transition from one focus
     * state to the other.
     *
     * @param showExpandedState Whether the url bar is expanded.
     * @param shouldShowKeyboard Whether the keyboard should be shown. This value is determined by
     *     whether url bar has got focus. Most of the time this is the same as showExpandedState,
     *     but in some cases, e.g. url bar is scrolled to the top of the screen on homepage but not
     *     focused, we set it differently.
     */
    public void finishUrlFocusChange(boolean showExpandedState, boolean shouldShowKeyboard) {
        mLocationBarMediator.finishUrlFocusChange(showExpandedState, shouldShowKeyboard);
    }

    /**
     * Toggles the mic button being shown when the location bar is not focused. By default the mic
     * button is not shown.
     */
    public void setShouldShowMicButtonWhenUnfocused(boolean shouldShowMicButtonWhenUnfocused) {
        mLocationBarMediator.setShouldShowMicButtonWhenUnfocusedForPhone(
                shouldShowMicButtonWhenUnfocused);
    }

    /** Updates the visibility of the buttons inside the location bar. */
    public void updateButtonVisibility() {
        mLocationBarMediator.updateButtonVisibility();
    }

    /**
     * @param show Whether the status icon background should be shown.
     */
    public void setStatusIconBackgroundVisibility(boolean show) {
        mStatusCoordinator.setStatusIconBackgroundVisibility(show);
    }

    /** Returns whether the layout is RTL. */
    public boolean isLayoutRtl() {
        return mLocationBarLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
    }

    // Tablet-specific methods.

    /**
     * Returns an animator to run for the given view when hiding buttons in the unfocused location
     * bar. This should also be used to create animators for hiding toolbar buttons.
     *
     * @param button The {@link View} of the button to hide.
     */
    public ObjectAnimator createHideButtonAnimatorForTablet(View button) {
        assert isTabletWindow();

        if (mLocationBarMediator != null) {
            return mLocationBarMediator.createHideButtonAnimatorForTablet(button);
        } else {
            return null;
        }
    }

    /**
     * Returns an animator to run for the given view when showing buttons in the unfocused location
     * bar. This should also be used to create animators for showing toolbar buttons.
     *
     * @param button The {@link View} of the button to show.
     */
    public ObjectAnimator createShowButtonAnimatorForTablet(View button) {
        assert isTabletWindow();
        return mLocationBarMediator.createShowButtonAnimatorForTablet(button);
    }

    /**
     * Creates animators for hiding buttons in the unfocused location bar. The buttons fade out
     * while width of the location bar gets larger. There are toolbar buttons that also hide at the
     * same time, causing the width of the location bar to change.
     *
     * @param toolbarStartPaddingDifference The difference in the toolbar's start padding between
     *     the beginning and end of the animation.
     * @return A list of animators to run.
     */
    public List<Animator> getHideButtonsWhenUnfocusedAnimatorsForTablet(
            int toolbarStartPaddingDifference) {
        assert isTabletWindow();
        return mLocationBarMediator.getHideButtonsWhenUnfocusedAnimatorsForTablet(
                toolbarStartPaddingDifference);
    }

    /**
     * Creates animators for showing buttons in the unfocused location bar. The buttons fade in
     * while width of the location bar gets smaller. There are toolbar buttons that also show at the
     * same time, causing the width of the location bar to change.
     *
     * @param toolbarStartPaddingDifference The difference in the toolbar's start padding between
     *     the beginning and end of the animation.
     * @return A list of animators to run.
     */
    public List<Animator> getShowButtonsWhenUnfocusedAnimatorsForTablet(
            int toolbarStartPaddingDifference) {
        assert isTabletWindow();
        return mLocationBarMediator.getShowButtonsWhenUnfocusedAnimatorsForTablet(
                toolbarStartPaddingDifference);
    }

    /** Toggles whether buttons should be displayed in the URL bar when it's not focused. */
    public void setShouldShowButtonsWhenUnfocusedForTablet(boolean shouldShowButtons) {
        assert isTabletWindow();
        mLocationBarMediator.setShouldShowButtonsWhenUnfocusedForTablet(shouldShowButtons);
    }

    // End tablet-specific methods.

    public void setVoiceRecognitionHandlerForTesting(
            VoiceRecognitionHandler voiceRecognitionHandler) {
        mLocationBarMediator.setVoiceRecognitionHandlerForTesting(voiceRecognitionHandler);
    }

    public void onUrlChangedForTesting() {
        mLocationBarMediator.onUrlChanged();
    }

    public void setLensControllerForTesting(LensController lensController) {
        mLocationBarMediator.setLensControllerForTesting(lensController);
    }

    private boolean isPhoneLayout() {
        return mLocationBarLayout instanceof LocationBarPhone;
    }

    private boolean isTabletLayout() {
        return mLocationBarLayout instanceof LocationBarTablet;
    }

    private boolean isTabletWindow() {
        return DeviceFormFactor.isWindowOnTablet(mWindowAndroid);
    }

    /* package */ LocationBarMediator getMediatorForTesting() {
        return mLocationBarMediator;
    }

    /**
     * @param isIncognito Whether we are currently in incognito mode.
     * @return The background color for the Omnibox suggestion dropdown list.
     */
    public @ColorInt int getDropdownBackgroundColor(boolean isIncognito) {
        return isIncognito ? mDropdownIncognitoBackgroundColor : mDropdownStandardBackgroundColor;
    }

    /**
     * @param isIncognito Whether we are currently in incognito mode.
     * @return The the background color for each individual suggestion.
     */
    public @ColorInt int getSuggestionBackgroundColor(boolean isIncognito) {
        return isIncognito
                ? mSuggestionIncognitoBackgroundColor
                : mSuggestionStandardBackgroundColor;
    }

    /**
     * @see LocationBarMediator#updateUrlBarHintTextColor(boolean)
     */
    public void updateUrlBarHintTextColor(boolean useDefaultUrlBarHintTextColor) {
        mLocationBarMediator.updateUrlBarHintTextColor(useDefaultUrlBarHintTextColor);
    }

    /**
     * @see LocationBarMediator#updateUrlActionContainerEndMargin(boolean)
     */
    public void updateUrlActionContainerEndMargin(boolean useDefaultUrlActionContainerEndMargin) {
        mLocationBarMediator.updateUrlActionContainerEndMargin(
                useDefaultUrlActionContainerEndMargin);
    }

    public int getUrlActionContainerEndMarginForTesting() {
        return mLocationBarLayout.getUrlActionContainerEndMarginForTesting(); // IN-TEST
    }

    /**
     * @return The location bar's {@link OmniboxSuggestionsVisualState}.
     */
    @Override
    public @NonNull Optional<OmniboxSuggestionsVisualState> getOmniboxSuggestionsVisualState() {
        return Optional.of(mAutocompleteCoordinator);
    }
}