chromium/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.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.app.appmenu;

import android.content.Context;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.widget.PopupMenu;

import androidx.annotation.ColorRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;

import com.google.common.primitives.UnsignedLongs;

import org.chromium.base.BuildInfo;
import org.chromium.base.CallbackController;
import org.chromium.base.ResettersForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.build.BuildConfig;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.PowerBookmarkUtils;
import org.chromium.chrome.browser.commerce.ShoppingFeatures;
import org.chromium.chrome.browser.commerce.ShoppingServiceFactory;
import org.chromium.chrome.browser.device.DeviceConditions;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController;
import org.chromium.chrome.browser.incognito.IncognitoUtils;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.night_mode.WebContentsDarkModeController;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.quick_delete.QuickDeleteController;
import org.chromium.chrome.browser.readaloud.ReadAloudController;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.share.ShareUtils;
import org.chromium.chrome.browser.sync.settings.SyncSettingsUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tinker_tank.TinkerTankDelegateImpl;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.translate.TranslateUtils;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler.AppMenuItemType;
import org.chromium.chrome.browser.ui.appmenu.AppMenuItemProperties;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.appmenu.AppMenuUtil;
import org.chromium.chrome.browser.ui.appmenu.CustomViewBinder;
import org.chromium.chrome.browser.util.BrowserUiUtils;
import org.chromium.chrome.browser.util.BrowserUiUtils.ModuleTypeOnStartAndNtp;
import org.chromium.chrome.browser.webapps.WebappRegistry;
import org.chromium.components.browser_ui.accessibility.PageZoomCoordinator;
import org.chromium.components.commerce.core.CommerceSubscription;
import org.chromium.components.commerce.core.IdentifierType;
import org.chromium.components.commerce.core.ManagementType;
import org.chromium.components.commerce.core.ShoppingService;
import org.chromium.components.commerce.core.SubscriptionType;
import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.components.webapps.AppBannerManager;
import org.chromium.components.webapps.WebappsUtils;
import org.chromium.net.ConnectionType;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.url.GURL;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Base implementation of {@link AppMenuPropertiesDelegate} that handles hiding and showing menu
 * items based on activity state.
 */
public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate {
    private static Boolean sItemBookmarkedForTesting;
    protected PropertyModel mReloadPropertyModel;

    protected final Context mContext;
    protected final boolean mIsTablet;
    protected final ActivityTabProvider mActivityTabProvider;
    protected final MultiWindowModeStateDispatcher mMultiWindowModeStateDispatcher;
    protected final TabModelSelector mTabModelSelector;
    protected final ToolbarManager mToolbarManager;
    protected final View mDecorView;

    private CallbackController mIncognitoReauthCallbackController = new CallbackController();
    private CallbackController mCallbackController = new CallbackController();
    private ObservableSupplier<BookmarkModel> mBookmarkModelSupplier;
    private boolean mUpdateMenuItemVisible;
    private ShareUtils mShareUtils;
    private final Supplier<ReadAloudController> mReadAloudControllerSupplier;
    private @Nullable ModelList mModelList;
    private int mReadAloudPos;
    @Nullable protected Runnable mReadAloudAppMenuResetter;
    private boolean mHasReadAloudInserted;

    /**
     * This is non null for the case of ChromeTabbedActivity when the corresponding {@link
     * CallbackController} has been fired.
     */
    private @Nullable IncognitoReauthController mIncognitoReauthController;

    @VisibleForTesting
    @IntDef({
        MenuGroup.INVALID,
        MenuGroup.PAGE_MENU,
        MenuGroup.OVERVIEW_MODE_MENU,
        MenuGroup.TABLET_EMPTY_MODE_MENU
    })
    @interface MenuGroup {
        int INVALID = -1;
        int PAGE_MENU = 0;
        int OVERVIEW_MODE_MENU = 1;
        int TABLET_EMPTY_MODE_MENU = 2;
    }

    // Please treat this list as append only and keep it in sync with
    // AppMenuHighlightItem in enums.xml.
    @IntDef({
        AppMenuHighlightItem.UNKNOWN,
        AppMenuHighlightItem.DOWNLOADS,
        AppMenuHighlightItem.BOOKMARKS,
        AppMenuHighlightItem.TRANSLATE,
        AppMenuHighlightItem.ADD_TO_HOMESCREEN,
        AppMenuHighlightItem.DOWNLOAD_THIS_PAGE,
        AppMenuHighlightItem.BOOKMARK_THIS_PAGE,
        AppMenuHighlightItem.DATA_REDUCTION_FOOTER
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface AppMenuHighlightItem {
        int UNKNOWN = 0;
        int DOWNLOADS = 1;
        int BOOKMARKS = 2;
        int TRANSLATE = 3;
        int ADD_TO_HOMESCREEN = 4;
        int DOWNLOAD_THIS_PAGE = 5;
        int BOOKMARK_THIS_PAGE = 6;
        int DATA_REDUCTION_FOOTER = 7;
        int NUM_ENTRIES = 8;
    }

    protected @Nullable LayoutStateProvider mLayoutStateProvider;
    protected Runnable mAppMenuInvalidator;

    /**
     * Construct a new {@link AppMenuPropertiesDelegateImpl}.
     *
     * @param context The activity context.
     * @param activityTabProvider The {@link ActivityTabProvider} for the containing activity.
     * @param multiWindowModeStateDispatcher The {@link MultiWindowModeStateDispatcher} for the
     *     containing activity.
     * @param tabModelSelector The {@link TabModelSelector} for the containing activity.
     * @param toolbarManager The {@link ToolbarManager} for the containing activity.
     * @param decorView The decor {@link View}, e.g. from Window#getDecorView(), for the containing
     *     activity.
     * @param layoutStateProvidersSupplier An {@link ObservableSupplier} for the {@link
     *     LayoutStateProvider} associated with the containing activity.
     * @param bookmarkModelSupplier An {@link ObservableSupplier} for the {@link BookmarkModel}
     * @param incognitoReauthControllerOneshotSupplier An {@link OneshotSupplier} for the {@link
     *     IncognitoReauthController} which is not null for tabbed Activity.
     */
    public AppMenuPropertiesDelegateImpl(
            Context context,
            ActivityTabProvider activityTabProvider,
            MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
            TabModelSelector tabModelSelector,
            ToolbarManager toolbarManager,
            View decorView,
            @Nullable OneshotSupplier<LayoutStateProvider> layoutStateProvidersSupplier,
            ObservableSupplier<BookmarkModel> bookmarkModelSupplier,
            @Nullable
                    OneshotSupplier<IncognitoReauthController>
                            incognitoReauthControllerOneshotSupplier,
            @Nullable Supplier<ReadAloudController> readAloudControllerSupplier) {
        mContext = context;
        mIsTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext);
        mActivityTabProvider = activityTabProvider;
        mMultiWindowModeStateDispatcher = multiWindowModeStateDispatcher;
        mTabModelSelector = tabModelSelector;
        mToolbarManager = toolbarManager;
        mDecorView = decorView;
        mReadAloudControllerSupplier = readAloudControllerSupplier;

        if (incognitoReauthControllerOneshotSupplier != null) {
            incognitoReauthControllerOneshotSupplier.onAvailable(
                    mIncognitoReauthCallbackController.makeCancelable(
                            incognitoReauthController -> {
                                mIncognitoReauthController = incognitoReauthController;
                            }));
        }

        if (layoutStateProvidersSupplier != null) {
            layoutStateProvidersSupplier.onAvailable(
                    mCallbackController.makeCancelable(
                            layoutStateProvider -> {
                                mLayoutStateProvider = layoutStateProvider;
                            }));
        }

        mBookmarkModelSupplier = bookmarkModelSupplier;
        mShareUtils = new ShareUtils();
    }

    @Override
    public void destroy() {
        if (mCallbackController != null) {
            mCallbackController.destroy();
            mCallbackController = null;
        }
        if (mReadAloudControllerSupplier.get() != null) {
            mReadAloudControllerSupplier
                    .get()
                    .removeReadabilityUpdateListener(mReadAloudAppMenuResetter);
        }
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    Runnable getReadAloudmenuResetter() {
        return mReadAloudAppMenuResetter;
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    @Nullable
    ModelList getModelList() {
        return mModelList;
    }

    /**
     * @return The resource id for the menu to use in {@link AppMenu}.
     */
    protected int getAppMenuLayoutId() {
        return R.menu.main_menu;
    }

    @Override
    public @Nullable List<CustomViewBinder> getCustomViewBinders() {
        List<CustomViewBinder> customViewBinders = new ArrayList<>();
        customViewBinders.add(
                new UpdateMenuItemViewBinder(mTabModelSelector.getModel(false).getProfile()));
        customViewBinders.add(new IncognitoMenuItemViewBinder());
        customViewBinders.add(new DividerLineMenuItemViewBinder());
        return customViewBinders;
    }

    /**
     * @return Whether the app menu for a web page should be shown.
     */
    protected boolean shouldShowPageMenu() {
        boolean isInTabSwitcher = isInTabSwitcher();
        if (mIsTablet) {
            boolean hasTabs = mTabModelSelector.getCurrentModel().getCount() != 0;
            return hasTabs && !isInTabSwitcher;
        } else {
            return !isInTabSwitcher;
        }
    }

    @VisibleForTesting
    @MenuGroup
    int getMenuGroup() {
        // Determine which menu to show.
        @MenuGroup int menuGroup = MenuGroup.INVALID;
        if (shouldShowPageMenu()) menuGroup = MenuGroup.PAGE_MENU;

        boolean isInTabSwitcher = isInTabSwitcher();
        if (mIsTablet) {
            boolean hasTabs = mTabModelSelector.getCurrentModel().getCount() != 0;
            if (hasTabs && isInTabSwitcher) {
                menuGroup = MenuGroup.OVERVIEW_MODE_MENU;
            } else if (!hasTabs) {
                menuGroup = MenuGroup.TABLET_EMPTY_MODE_MENU;
            }
        } else if (isInTabSwitcher) {
            menuGroup = MenuGroup.OVERVIEW_MODE_MENU;
        }
        assert menuGroup != MenuGroup.INVALID;
        return menuGroup;
    }

    /**
     * @return Whether the grid tab switcher is showing.
     */
    private boolean isInTabSwitcher() {
        return mLayoutStateProvider != null
                && mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER)
                && !mLayoutStateProvider.isLayoutStartingToHide(LayoutType.TAB_SWITCHER);
    }

    private void setMenuGroupVisibility(@MenuGroup int menuGroup, Menu menu) {
        menu.setGroupVisible(R.id.PAGE_MENU, menuGroup == MenuGroup.PAGE_MENU);
        menu.setGroupVisible(R.id.OVERVIEW_MODE_MENU, menuGroup == MenuGroup.OVERVIEW_MODE_MENU);
        menu.setGroupVisible(
                R.id.TABLET_EMPTY_MODE_MENU, menuGroup == MenuGroup.TABLET_EMPTY_MODE_MENU);
    }

    @Override
    public ModelList getMenuItems(
            CustomItemViewTypeProvider customItemViewTypeProvider, AppMenuHandler handler) {
        mReadAloudPos = -1;
        PopupMenu popup = new PopupMenu(mContext, mDecorView);
        Menu menu = popup.getMenu();
        MenuInflater inflater = popup.getMenuInflater();
        inflater.inflate(getAppMenuLayoutId(), menu);

        return getMenuItemsForMenu(menu, customItemViewTypeProvider, handler);
    }

    @VisibleForTesting
    ModelList getMenuItemsForMenu(
            Menu menu,
            CustomItemViewTypeProvider customItemViewTypeProvider,
            AppMenuHandler handler) {
        ModelList modelList = new ModelList();
        prepareMenu(menu, handler);
        // TODO(crbug.com/40145539): Programmatically create menu item's PropertyModel instead of
        // converting from MenuItems.
        int visibleBeforeReadAloudCount = 0;
        for (int i = 0; i < menu.size(); ++i) {
            MenuItem item = menu.getItem(i);
            if (!item.isVisible()) {
                if (item.getItemId() == R.id.readaloud_menu_id) {
                    mReadAloudPos = visibleBeforeReadAloudCount;
                }
                continue;
            }
            visibleBeforeReadAloudCount++;
            PropertyModel propertyModel = AppMenuUtil.menuItemToPropertyModel(item);
            propertyModel.set(AppMenuItemProperties.ICON_COLOR_RES, getMenuItemIconColorRes(item));
            propertyModel.set(
                    AppMenuItemProperties.ICON_SHOW_BADGE, shouldShowBadgeOnMenuItemIcon(item));
            propertyModel.set(AppMenuItemProperties.SUPPORT_ENTER_ANIMATION, true);
            propertyModel.set(AppMenuItemProperties.MENU_ICON_AT_START, isMenuIconAtStart());
            propertyModel.set(AppMenuItemProperties.TITLE_CONDENSED, getContentDescription(item));
            propertyModel.set(AppMenuItemProperties.MANAGED, isMenuItemManaged(item));
            if (item.hasSubMenu()) {
                // Only support top level menu items have SUBMENU, and a SUBMENU item cannot have a
                // SUBMENU.
                // TODO(crbug.com/40171109) : Create a new SubMenuItemProperties property key set
                // for
                // SUBMENU items.
                ModelList subList = new ModelList();
                for (int j = 0; j < item.getSubMenu().size(); ++j) {
                    MenuItem subitem = item.getSubMenu().getItem(j);
                    if (!subitem.isVisible()) continue;

                    PropertyModel subModel = AppMenuUtil.menuItemToPropertyModel(subitem);
                    subList.add(new MVCListAdapter.ListItem(0, subModel));
                    if (subitem.getItemId() == R.id.reload_menu_id) {
                        mReloadPropertyModel = subModel;
                        Tab currentTab = mActivityTabProvider.get();
                        loadingStateChanged(currentTab == null ? false : currentTab.isLoading());
                    }
                }
                propertyModel.set(AppMenuItemProperties.SUBMENU, subList);
            }
            int menutype = AppMenuItemType.STANDARD;
            if (item.getItemId() == R.id.request_desktop_site_row_menu_id
                    || item.getItemId() == R.id.share_row_menu_id
                    || item.getItemId() == R.id.auto_dark_web_contents_row_menu_id) {
                menutype = AppMenuItemType.TITLE_BUTTON;
            } else if (item.getItemId() == R.id.icon_row_menu_id) {
                int viewCount = item.getSubMenu().size();
                if (viewCount == 3) {
                    menutype = AppMenuItemType.THREE_BUTTON_ROW;
                } else if (viewCount == 4) {
                    menutype = AppMenuItemType.FOUR_BUTTON_ROW;
                } else if (viewCount == 5) {
                    menutype = AppMenuItemType.FIVE_BUTTON_ROW;
                }
            } else {
                // Could be standard items or custom items.
                int customType = customItemViewTypeProvider.fromMenuItemId(item.getItemId());
                if (customType != CustomViewBinder.NOT_HANDLED) {
                    menutype = customType;
                }
            }
            modelList.add(new MVCListAdapter.ListItem(menutype, propertyModel));
        }
        mModelList = modelList;
        return modelList;
    }

    @Override
    public void prepareMenu(Menu menu, AppMenuHandler handler) {
        int menuGroup = getMenuGroup();
        setMenuGroupVisibility(menuGroup, menu);

        boolean isIncognito = mTabModelSelector.getCurrentModel().isIncognito();
        Tab currentTab = mActivityTabProvider.get();

        if (menuGroup == MenuGroup.PAGE_MENU) {
            preparePageMenu(menu, currentTab, handler, isIncognito);
        }
        prepareCommonMenuItems(menu, menuGroup, isIncognito);
    }

    /** Prepare the menu items. Note: it is possible that currentTab is null. */
    private void preparePageMenu(
            Menu menu, @Nullable Tab currentTab, AppMenuHandler handler, boolean isIncognito) {
        // Multiple menu items shouldn't be enabled when the currentTab is null. Use a flag to
        // indicate whether the current Tab isn't null.
        boolean isCurrentTabNotNull = currentTab != null;

        GURL url = isCurrentTabNotNull ? currentTab.getUrl() : GURL.emptyGURL();
        final boolean isNativePage =
                url.getScheme().equals(UrlConstants.CHROME_SCHEME)
                        || url.getScheme().equals(UrlConstants.CHROME_NATIVE_SCHEME)
                        || (isCurrentTabNotNull && currentTab.isNativePage());
        final boolean isFileScheme = url.getScheme().equals(UrlConstants.FILE_SCHEME);
        final boolean isContentScheme = url.getScheme().equals(UrlConstants.CONTENT_SCHEME);

        // Update the icon row items (shown in narrow form factors).
        boolean shouldShowIconRow = shouldShowIconRow();
        menu.findItem(R.id.icon_row_menu_id).setVisible(shouldShowIconRow);
        if (shouldShowIconRow) {
            SubMenu actionBar = menu.findItem(R.id.icon_row_menu_id).getSubMenu();

            // Disable the "Forward" menu item if there is no page to go to.
            MenuItem forwardMenuItem = actionBar.findItem(R.id.forward_menu_id);
            forwardMenuItem.setEnabled(isCurrentTabNotNull && currentTab.canGoForward());

            Drawable icon = AppCompatResources.getDrawable(mContext, R.drawable.btn_reload_stop);
            DrawableCompat.setTintList(
                    icon,
                    AppCompatResources.getColorStateList(
                            mContext, R.color.default_icon_color_tint_list));
            actionBar.findItem(R.id.reload_menu_id).setIcon(icon);
            loadingStateChanged(isCurrentTabNotNull && currentTab.isLoading());

            MenuItem bookmarkMenuItemShortcut = actionBar.findItem(R.id.bookmark_this_page_id);
            updateBookmarkMenuItemShortcut(
                    bookmarkMenuItemShortcut, currentTab, /* fromCCT= */ false);

            MenuItem offlineMenuItem = actionBar.findItem(R.id.offline_page_id);
            offlineMenuItem.setEnabled(isCurrentTabNotNull && shouldEnableDownloadPage(currentTab));

            if (!isCurrentTabNotNull) {
                actionBar.findItem(R.id.info_menu_id).setEnabled(false);
                actionBar.findItem(R.id.reload_menu_id).setEnabled(false);
            }
            assert actionBar.size() == 5;
        }

        mUpdateMenuItemVisible = shouldShowUpdateMenuItem();
        menu.findItem(R.id.update_menu_id).setVisible(mUpdateMenuItemVisible);
        if (mUpdateMenuItemVisible) {
            mAppMenuInvalidator = () -> handler.invalidateAppMenu();
            UpdateMenuItemHelper.getInstance(mTabModelSelector.getModel(false).getProfile())
                    .registerObserver(mAppMenuInvalidator);
        }

        menu.findItem(R.id.new_window_menu_id).setVisible(shouldShowNewWindow());
        menu.findItem(R.id.move_to_other_window_menu_id).setVisible(shouldShowMoveToOtherWindow());
        MenuItem menu_all_windows = menu.findItem(R.id.manage_all_windows_menu_id);
        boolean showManageAllWindows = shouldShowManageAllWindows();
        menu_all_windows.setVisible(showManageAllWindows);
        if (showManageAllWindows) {
            menu_all_windows.setTitle(
                    mContext.getString(R.string.menu_manage_all_windows, getInstanceCount()));
        }

        updatePriceTrackingMenuItemRow(
                menu.findItem(R.id.enable_price_tracking_menu_id),
                menu.findItem(R.id.disable_price_tracking_menu_id),
                currentTab);

        // Don't allow either "chrome://" pages or interstitial pages to be shared, or when the
        // current tab is null.
        menu.findItem(R.id.share_row_menu_id)
                .setVisible(isCurrentTabNotNull && mShareUtils.shouldEnableShare(currentTab));

        if (isCurrentTabNotNull) {
            updateDirectShareMenuItem(menu.findItem(R.id.direct_share_menu_id));
        }

        menu.findItem(R.id.paint_preview_show_id)
                .setVisible(
                        isCurrentTabNotNull
                                && shouldShowPaintPreview(isNativePage, currentTab, isIncognito));

        // Enable image descriptions if touch exploration is currently enabled, but not on the
        // native NTP.
        if (isCurrentTabNotNull
                && shouldShowWebContentsDependentMenuItem(currentTab)
                && ImageDescriptionsController.getInstance()
                        .shouldShowImageDescriptionsMenuItem()) {
            menu.findItem(R.id.get_image_descriptions_id).setVisible(true);

            int titleId = R.string.menu_stop_image_descriptions;
            Profile profile = currentTab.getProfile();
            // If image descriptions are not enabled, then we want the menu item to be "Get".
            if (!ImageDescriptionsController.getInstance().imageDescriptionsEnabled(profile)) {
                titleId = R.string.menu_get_image_descriptions;
            } else if (ImageDescriptionsController.getInstance().onlyOnWifiEnabled(profile)
                    && DeviceConditions.getCurrentNetConnectionType(mContext)
                            != ConnectionType.CONNECTION_WIFI) {
                // If image descriptions are enabled, then we want "Stop", except in the special
                // case that the user specified only on Wifi, and we are not currently on Wifi.
                titleId = R.string.menu_get_image_descriptions;
            }

            menu.findItem(R.id.get_image_descriptions_id).setTitle(titleId);
        } else {
            menu.findItem(R.id.get_image_descriptions_id).setVisible(false);
        }

        // Conditionally add the Zoom menu item, but not on the native NTP.
        menu.findItem(R.id.page_zoom_id)
                .setVisible(
                        isCurrentTabNotNull
                                && shouldShowWebContentsDependentMenuItem(currentTab)
                                && PageZoomCoordinator.shouldShowMenuItem());

        // Disable find in page on the native NTP (except for PDF native page).
        updateFindInPageMenuItem(menu, currentTab);

        // Prepare translate menu button.
        prepareTranslateMenuItem(menu, currentTab);

        // Set visibility of Read Aloud menu item.
        prepareReadAloudMenuItem(menu, currentTab);

        prepareAddToHomescreenMenuItem(
                menu,
                currentTab,
                shouldShowHomeScreenMenuItem(
                        isNativePage, isFileScheme, isContentScheme, isIncognito, url));

        updateRequestDesktopSiteMenuItem(menu, currentTab, true /* can show */, isNativePage);

        updateAutoDarkMenuItem(menu, currentTab, isNativePage);

        // Only display reader mode settings menu option if the current page is in reader mode.
        menu.findItem(R.id.reader_mode_prefs_id)
                .setVisible(isCurrentTabNotNull && shouldShowReaderModePrefs(currentTab));

        updateManagedByMenuItem(menu, currentTab);

        // Only display quick delete divider line on the page menu and if quick delete is enabled.
        menu.findItem(R.id.quick_delete_divider_line_id)
                .setVisible(isQuickDeleteEnabled(isIncognito));
    }

    /**
     * @return The number of Chrome instances either running alive or dormant but the state
     *         is present for restoration.
     */
    @VisibleForTesting
    int getInstanceCount() {
        return mMultiWindowModeStateDispatcher.getInstanceCount();
    }

    private void prepareCommonMenuItems(Menu menu, @MenuGroup int menuGroup, boolean isIncognito) {
        // We have to iterate all menu items since same menu item ID may be associated with more
        // than one menu items.
        boolean isOverviewModeMenu = menuGroup == MenuGroup.OVERVIEW_MODE_MENU;

        // Disable incognito group and select tabs when a re-authentication screen is shown.
        // We show the re-auth screen only in Incognito mode.
        boolean isIncognitoReauthShowing =
                isIncognito
                        && (mIncognitoReauthController != null)
                        && mIncognitoReauthController.isReauthPageShowing();

        boolean isMenuSelectTabsVisible = isOverviewModeMenu;
        boolean isMenuSelectTabsEnabled =
                !isIncognitoReauthShowing
                        && isMenuSelectTabsVisible
                        && mTabModelSelector.isTabStateInitialized()
                        && mTabModelSelector
                                        .getTabModelFilterProvider()
                                        .getCurrentTabModelFilter()
                                        .getCount()
                                != 0;

        boolean hasItemBetweenDividers = false;

        for (int i = 0; i < menu.size(); ++i) {
            MenuItem item = menu.getItem(i);
            if (!shouldShowIconBeforeItem()) {
                // Remove icons for menu items except the reader mode prefs and the update menu
                // item.
                if (item.getItemId() != R.id.reader_mode_prefs_id
                        && item.getItemId() != R.id.update_menu_id) {
                    item.setIcon(null);
                }

                // Remove title button icons.
                if (item.getItemId() == R.id.request_desktop_site_row_menu_id
                        || item.getItemId() == R.id.share_row_menu_id
                        || item.getItemId() == R.id.auto_dark_web_contents_row_menu_id) {
                    item.getSubMenu().getItem(0).setIcon(null);
                }
            }

            if (item.getItemId() == R.id.new_incognito_tab_menu_id && item.isVisible()) {
                // Disable new incognito tab when it is blocked (e.g. by a policy).
                // findItem(...).setEnabled(...)" is not enough here, because of the inflated
                // main_menu.xml contains multiple items with the same id in different groups
                // e.g.: menu_new_incognito_tab.
                // Disable new incognito tab when a re-authentication might be showing.
                item.setEnabled(isIncognitoEnabled() && !isIncognitoReauthShowing);
            }

            if (item.getItemId() == R.id.divider_line_id) {
                item.setEnabled(false);
            }

            int itemGroupId = item.getGroupId();
            if (!(menuGroup == MenuGroup.OVERVIEW_MODE_MENU
                            && itemGroupId == R.id.OVERVIEW_MODE_MENU
                    || menuGroup == MenuGroup.PAGE_MENU && itemGroupId == R.id.PAGE_MENU)) {
                continue;
            }

            if (item.getItemId() == R.id.recent_tabs_menu_id) {
                item.setVisible(!isIncognito);
            }
            if (item.getItemId() == R.id.menu_select_tabs) {
                item.setVisible(isMenuSelectTabsVisible);
                item.setEnabled(isMenuSelectTabsEnabled);
            }
            if (item.getItemId() == R.id.close_all_tabs_menu_id) {
                boolean hasTabs = mTabModelSelector.getTotalTabCount() > 0;
                item.setVisible(!isIncognito && isOverviewModeMenu);
                item.setEnabled(hasTabs);
            }
            if (item.getItemId() == R.id.close_all_incognito_tabs_menu_id) {
                boolean hasIncognitoTabs = mTabModelSelector.getModel(true).getCount() > 0;
                item.setVisible(isIncognito && isOverviewModeMenu);
                item.setEnabled(hasIncognitoTabs);
            }
            if (item.getItemId() == R.id.quick_delete_menu_id) {
                item.setVisible(isQuickDeleteEnabled(isIncognito));
                item.setEnabled(isQuickDeleteEnabled(isIncognito));
            }
            if (item.getItemId() == R.id.tinker_tank_menu_id) {
                item.setVisible(TinkerTankDelegateImpl.enabled());
                item.setEnabled(TinkerTankDelegateImpl.enabled());
            }

            // This needs to be done after the visibility of the item is set.
            if (item.getItemId() == R.id.divider_line_id) {
                if (!hasItemBetweenDividers) {
                    // If there isn't any visible menu items between the two divider lines, mark
                    // this line invisible.
                    item.setVisible(false);
                } else {
                    hasItemBetweenDividers = false;
                }
            } else if (!hasItemBetweenDividers && item.isVisible()) {
                // When the item isn't a divider line and is visible, we set hasItemBetweenDividers
                // to be true.
                hasItemBetweenDividers = true;
            }
        }
    }

    /**
     * @param isIncognito Whether the currentTab is incognito.
     * @return Whether the quick delete menu item should be enabled.
     */
    private boolean isQuickDeleteEnabled(boolean isIncognito) {
        return !isIncognito && QuickDeleteController.isQuickDeleteEnabled();
    }

    /**
     * @param currentTab The currentTab for which the app menu is showing.
     * @return Whether the reader mode preferences menu item should be displayed.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public boolean shouldShowReaderModePrefs(@NonNull Tab currentTab) {
        return DomDistillerUrlUtils.isDistilledPage(currentTab.getUrl());
    }

    /**
     * @param currentTab The currentTab for which the app menu is showing.
     * @return Whether the {@code currentTab} may be downloaded, indicating whether the download
     *         page menu item should be enabled.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public boolean shouldEnableDownloadPage(@NonNull Tab currentTab) {
        return DownloadUtils.isAllowedToDownloadPage(currentTab);
    }

    /**
     * @param currentTab The currentTab for which the app menu is showing.
     * @return Whether bookmark page menu item should be checked, indicating that the current tab
     *         is bookmarked.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public boolean shouldCheckBookmarkStar(@NonNull Tab currentTab) {
        if (sItemBookmarkedForTesting != null) return sItemBookmarkedForTesting;

        if (!mBookmarkModelSupplier.hasValue()) return false;
        return mBookmarkModelSupplier.get().hasBookmarkIdForTab(currentTab);
    }

    /**
     * @return Whether the update Chrome menu item should be displayed.
     */
    protected boolean shouldShowUpdateMenuItem() {
        return UpdateMenuItemHelper.getInstance(mTabModelSelector.getModel(false).getProfile())
                        .getUiState()
                        .itemState
                != null;
    }

    /**
     * @return Whether the "Move to other window" menu item should be displayed.
     */
    protected boolean shouldShowMoveToOtherWindow() {
        if (!instanceSwitcherWithMultiInstanceEnabled() && shouldShowNewWindow()) return false;
        return mMultiWindowModeStateDispatcher.isMoveToOtherWindowSupported(mTabModelSelector);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    public boolean instanceSwitcherWithMultiInstanceEnabled() {
        return MultiWindowUtils.instanceSwitcherEnabled()
                && MultiWindowUtils.isMultiInstanceApi31Enabled();
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    public boolean isTabletSizeScreen() {
        return mIsTablet;
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
    public boolean isAutoDarkWebContentsEnabled() {
        Profile profile = mTabModelSelector.getCurrentModel().getProfile();
        assert profile != null;
        boolean isFlagEnabled =
                ChromeFeatureList.isEnabled(
                        ChromeFeatureList.DARKEN_WEBSITES_CHECKBOX_IN_THEMES_SETTING);
        boolean isFeatureEnabled =
                WebContentsDarkModeController.isFeatureEnabled(mContext, profile);
        return isFlagEnabled && isFeatureEnabled;
    }

    /**
     * @return Whether the "New window" menu item should be displayed.
     */
    protected boolean shouldShowNewWindow() {
        // Hide the menu on automotive devices.
        if (BuildInfo.getInstance().isAutomotive) return false;

        if (instanceSwitcherWithMultiInstanceEnabled()) {
            // Hide the menu if we already have the maximum number of windows.
            if (getInstanceCount() >= MultiWindowUtils.getMaxInstances()) return false;

            // On phones, show the menu only when in split-screen, with a single instance
            // running on the foreground.
            return isTabletSizeScreen()
                    || (!mMultiWindowModeStateDispatcher.isChromeRunningInAdjacentWindow()
                            && (mMultiWindowModeStateDispatcher.isInMultiWindowMode()
                                    || mMultiWindowModeStateDispatcher.isInMultiDisplayMode()));
        } else {
            if (mMultiWindowModeStateDispatcher.isMultiInstanceRunning()) return false;
            return (mMultiWindowModeStateDispatcher.canEnterMultiWindowMode()
                            && isTabletSizeScreen())
                    || mMultiWindowModeStateDispatcher.isInMultiWindowMode()
                    || mMultiWindowModeStateDispatcher.isInMultiDisplayMode();
        }
    }

    private boolean shouldShowManageAllWindows() {
        return MultiWindowUtils.shouldShowManageWindowsMenu();
    }

    /**
     * @param isNativePage Whether the current tab is a native page.
     * @param currentTab The currentTab for which the app menu is showing.
     * @param isIncognito Whether the currentTab is incognito.
     * @return Whether the paint preview menu item should be displayed.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public boolean shouldShowPaintPreview(
            boolean isNativePage, @NonNull Tab currentTab, boolean isIncognito) {
        return ChromeFeatureList.sPaintPreviewDemo.isEnabled() && !isNativePage && !isIncognito;
    }

    /**
     * @param currentTab The currentTab for which the app menu is showing.
     * @return Whether the currentTab should show an app menu item that requires a webContents. This
     *     will return false for native NTP, and true otherwise.
     */
    protected boolean shouldShowWebContentsDependentMenuItem(@NonNull Tab currentTab) {
        return !currentTab.isNativePage() && currentTab.getWebContents() != null;
    }

    /**
     * This method should only be called once per context menu shown.
     * @param currentTab The currentTab for which the app menu is showing.
     * @param logging Whether logging should be performed in this check.
     * @return Whether the translate menu item should be displayed.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public boolean shouldShowTranslateMenuItem(@NonNull Tab currentTab) {
        return TranslateUtils.canTranslateCurrentTab(currentTab, true);
    }

    /**
     * @param isNativePage Whether the current tab is a native page.
     * @param isFileScheme Whether URL for the current tab starts with the file:// scheme.
     * @param isContentScheme Whether URL for the current tab starts with the file:// scheme.
     * @param isIncognito Whether the current tab is incognito.
     * @param url The URL for the current tab.
     * @return Whether the homescreen menu item should be displayed.
     */
    protected boolean shouldShowHomeScreenMenuItem(
            boolean isNativePage,
            boolean isFileScheme,
            boolean isContentScheme,
            boolean isIncognito,
            @NonNull GURL url) {
        // Hide 'Add to homescreen' for the following:
        // * native pages - Android doesn't know how to direct those URLs.
        // * incognito pages - To avoid problems where users create shortcuts in incognito
        //                      mode and then open the webapp in regular mode.
        // * file:// - After API 24, file: URIs are not supported in VIEW intents and thus
        //             can not be added to the homescreen.
        // * content:// - Accessing external content URIs requires the calling app to grant
        //                access to the resource via FLAG_GRANT_READ_URI_PERMISSION, and that
        //                is not persisted when adding to the homescreen.
        // * If creating shortcuts it not supported by the current home screen.
        return WebappsUtils.isAddToHomeIntentSupported()
                && !isNativePage
                && !isFileScheme
                && !isContentScheme
                && !isIncognito
                && !url.isEmpty();
    }

    /**
     * @param currentTab Current tab being displayed.
     * @return Whether the "Managed by your organization" menu item should be displayed.
     */
    protected boolean shouldShowManagedByMenuItem(Tab currentTab) {
        return false;
    }

    /** Sets the visibility and labels of the "Add to Home screen" and "Open WebAPK" menu items. */
    protected void prepareAddToHomescreenMenuItem(
            Menu menu, Tab currentTab, boolean shouldShowHomeScreenMenuItem) {
        MenuItem universalInstallItem = menu.findItem(R.id.universal_install);
        MenuItem openWebApkItem = menu.findItem(R.id.open_webapk_id);

        universalInstallItem.setVisible(false);
        openWebApkItem.setVisible(false);

        if (currentTab == null || !shouldShowHomeScreenMenuItem) {
            return;
        }

        long addToHomeScreenStart = SystemClock.elapsedRealtime();
        ResolveInfo resolveInfo = queryWebApkResolveInfo(mContext, currentTab);
        RecordHistogram.recordTimesHistogram(
                "Android.PrepareMenu.OpenWebApkVisibilityCheck",
                SystemClock.elapsedRealtime() - addToHomeScreenStart);

        // When Universal Install is active, we only show this menu item if we are browsing
        // the root page of an already installed app.
        boolean openWebApkItemVisible =
                resolveInfo != null
                        && resolveInfo.activityInfo.packageName != null
                        && "/".equals(currentTab.getUrl().getPath());

        if (openWebApkItemVisible) {
            // This is the 'webapp is already installed' case, so we offer to open the webapp.
            String appName = resolveInfo.loadLabel(mContext.getPackageManager()).toString();
            openWebApkItem.setTitle(mContext.getString(R.string.menu_open_webapk, appName));
            openWebApkItem.setVisible(true);
            return;
        }

        universalInstallItem.setVisible(true);
    }

    public static ResolveInfo queryWebApkResolveInfo(Context context, Tab currentTab) {
        String manifestId = AppBannerManager.maybeGetManifestId(currentTab.getWebContents());
        ResolveInfo resolveInfo =
                WebApkValidator.queryFirstWebApkResolveInfo(
                        context,
                        currentTab.getUrl().getSpec(),
                        WebappRegistry.getInstance().findWebApkWithManifestId(manifestId));

        if (resolveInfo == null) {
            // If a WebAPK with matching manifestId can't be found, fallback to query without it.
            resolveInfo =
                    WebApkValidator.queryFirstWebApkResolveInfo(
                            context, currentTab.getUrl().getSpec());
        }

        return resolveInfo;
    }

    @Override
    public Bundle getBundleForMenuItem(int itemId) {
        return null;
    }

    /** Sets the visibility of the "Translate" menu item. */
    protected void prepareTranslateMenuItem(Menu menu, @Nullable Tab currentTab) {
        boolean isTranslateVisible = currentTab != null && shouldShowTranslateMenuItem(currentTab);
        menu.findItem(R.id.translate_id).setVisible(isTranslateVisible);
    }

    /** Sets visibility of the "Listen to this page" menu item. */
    protected void prepareReadAloudMenuItem(Menu menu, @Nullable Tab currentTab) {
        boolean visible = false;

        if (mReadAloudControllerSupplier.get() != null) {
            ReadAloudController readAloudController = mReadAloudControllerSupplier.get();
            visible =
                    readAloudController != null
                            && currentTab != null
                            && readAloudController.isReadable(currentTab);

            if (mReadAloudAppMenuResetter == null) {
                mReadAloudAppMenuResetter =
                        () -> {
                            boolean isReadable =
                                    mReadAloudControllerSupplier.get().isReadable(currentTab);
                            MenuItem item = menu.findItem(R.id.readaloud_menu_id);
                            if (isReadable) {
                                maybeInsertReadAloudItem(item);
                            } else {
                                maybeFindAndRemoveReadAloudItem(item);
                            }
                        };

                readAloudController.addReadabilityUpdateListener(mReadAloudAppMenuResetter);
            }
        }
        mHasReadAloudInserted = visible;
        menu.findItem(R.id.readaloud_menu_id).setVisible(visible);
    }

    /**
     * Try finding ReadAloud in the mModelList (being in the model means it was visible in the app
     * menu). If found, remove it from the model, update MenuItem visibility state and update the
     * last position on the read aloud item in the menu.
     */
    private void maybeFindAndRemoveReadAloudItem(MenuItem item) {
        if (mModelList == null) {
            return;
        }
        Iterator<ListItem> it = mModelList.iterator();
        int counter = 0;
        while (it.hasNext()) {
            ListItem li = it.next();
            int id = li.model.get(AppMenuItemProperties.MENU_ITEM_ID);
            if (id == item.getItemId()) {
                mReadAloudPos = counter;
                mModelList.remove(li);
                mHasReadAloudInserted = false;
                return;
            }
            counter++;
        }
    }

    /** If ReadAloud is not present in the mModelList, insert it at the saved position. */
    private void maybeInsertReadAloudItem(MenuItem item) {
        if (mModelList == null) {
            return;
        }
        // Already on the list, return early
        if (mHasReadAloudInserted) {
            return;
        }

        // now try to insert it.
        assert mReadAloudPos != -1 : "Unexpectedly missing position for the read aloud menu item";
        if (mReadAloudPos != -1) {
            item.setVisible(true);
            mHasReadAloudInserted = true;
            PropertyModel propertyModel = AppMenuUtil.menuItemToPropertyModel(item);
            propertyModel.set(AppMenuItemProperties.ICON_COLOR_RES, getMenuItemIconColorRes(item));
            propertyModel.set(AppMenuItemProperties.SUPPORT_ENTER_ANIMATION, true);
            propertyModel.set(AppMenuItemProperties.MENU_ICON_AT_START, isMenuIconAtStart());
            mModelList.add(
                    mReadAloudPos,
                    new MVCListAdapter.ListItem(AppMenuItemType.STANDARD, propertyModel));
        }
    }

    /** Return whether the given {@link MenuItem} is managed by policy. */
    protected boolean isMenuItemManaged(MenuItem item) {
        if (item.getItemId() == R.id.new_incognito_tab_menu_id) {
            return IncognitoUtils.isIncognitoModeManaged(
                    mTabModelSelector.getCurrentModel().getProfile());
        }
        return false;
    }

    /** Returns true if a badge (i.e. a red-dot) should be shown on the menu item icon. */
    protected boolean shouldShowBadgeOnMenuItemIcon(MenuItem item) {
        if (item.getItemId() == R.id.preferences_id) {
            // Theoretically mTabModelSelector could return a stub model.
            Profile profile = mTabModelSelector.getCurrentModel().getProfile();
            if (profile == null) {
                return false;
            }
            // Return true if there is any identity error(for signed-in users) or sync error(for
            // syncing users).
            return SyncSettingsUtils.getIdentityError(profile)
                            != SyncSettingsUtils.SyncError.NO_ERROR
                    || SyncSettingsUtils.getSyncError(profile)
                            != SyncSettingsUtils.SyncError.NO_ERROR;
        }
        return false;
    }

    /**
     * Returns content description for the menu item, if different from the titleCondensed xml
     * attribute.
     */
    protected String getContentDescription(MenuItem item) {
        if (item.getItemId() == R.id.preferences_id) {
            // Theoretically mTabModelSelector could return a stub model.
            Profile profile = mTabModelSelector.getCurrentModel().getProfile();
            if (profile == null) {
                return null;
            }
            if (SyncSettingsUtils.getIdentityError(profile) != SyncSettingsUtils.SyncError.NO_ERROR
                    || SyncSettingsUtils.getSyncError(profile)
                            != SyncSettingsUtils.SyncError.NO_ERROR) {
                return mContext.getResources().getString(R.string.menu_settings_account_error);
            }
        }
        return null;
    }

    @Override
    public void loadingStateChanged(boolean isLoading) {
        if (mReloadPropertyModel != null) {
            Resources resources = mContext.getResources();
            mReloadPropertyModel
                    .get(AppMenuItemProperties.ICON)
                    .setLevel(
                            isLoading
                                    ? resources.getInteger(R.integer.reload_button_level_stop)
                                    : resources.getInteger(R.integer.reload_button_level_reload));
            mReloadPropertyModel.set(
                    AppMenuItemProperties.TITLE,
                    resources.getString(
                            isLoading
                                    ? R.string.accessibility_btn_stop_loading
                                    : R.string.accessibility_btn_refresh));
            mReloadPropertyModel.set(
                    AppMenuItemProperties.TITLE_CONDENSED,
                    resources.getString(isLoading ? R.string.menu_stop_refresh : R.string.refresh));
        }
    }

    @Override
    public void onMenuDismissed() {
        mReloadPropertyModel = null;
        if (mUpdateMenuItemVisible) {
            UpdateMenuItemHelper updateHelper =
                    UpdateMenuItemHelper.getInstance(
                            mTabModelSelector.getModel(false).getProfile());
            updateHelper.onMenuDismissed();
            updateHelper.unregisterObserver(mAppMenuInvalidator);
            mUpdateMenuItemVisible = false;
            mAppMenuInvalidator = null;
        }
    }

    @VisibleForTesting
    boolean shouldShowIconRow() {
        boolean shouldShowIconRow =
                mIsTablet
                        ? mDecorView.getWidth()
                                < DeviceFormFactor.getNonMultiDisplayMinimumTabletWidthPx(mContext)
                        : true;

        final boolean isMenuButtonOnTop = mToolbarManager != null;
        shouldShowIconRow &= isMenuButtonOnTop;
        return shouldShowIconRow;
    }

    @Override
    public int getFooterResourceId() {
        return 0;
    }

    @Override
    public int getHeaderResourceId() {
        return 0;
    }

    @Override
    public int getGroupDividerId() {
        return R.id.divider_line_id;
    }

    @Override
    public boolean shouldShowFooter(int maxMenuHeight) {
        return true;
    }

    @Override
    public boolean shouldShowHeader(int maxMenuHeight) {
        return true;
    }

    @Override
    public void onFooterViewInflated(AppMenuHandler appMenuHandler, View view) {}

    @Override
    public void onHeaderViewInflated(AppMenuHandler appMenuHandler, View view) {}

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

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

    /**
     * Updates the bookmark item's visibility.
     *
     * @param bookmarkMenuItemShortcut {@link MenuItem} for adding/editing the bookmark.
     * @param currentTab Current tab being displayed.
     */
    protected void updateBookmarkMenuItemShortcut(
            MenuItem bookmarkMenuItemShortcut, @Nullable Tab currentTab, boolean fromCCT) {
        if (!mBookmarkModelSupplier.hasValue() || currentTab == null) {
            // If the BookmarkModel still isn't available, assume the bookmark menu item is not
            // editable.
            bookmarkMenuItemShortcut.setEnabled(false);
        } else {
            bookmarkMenuItemShortcut.setEnabled(
                    mBookmarkModelSupplier.get().isEditBookmarksEnabled());
        }

        if (currentTab != null && shouldCheckBookmarkStar(currentTab)) {
            bookmarkMenuItemShortcut.setIcon(R.drawable.btn_star_filled);
            bookmarkMenuItemShortcut.setChecked(true);
            bookmarkMenuItemShortcut.setTitleCondensed(mContext.getString(R.string.edit_bookmark));
        } else {
            bookmarkMenuItemShortcut.setIcon(R.drawable.star_outline_24dp);
            bookmarkMenuItemShortcut.setChecked(false);
            bookmarkMenuItemShortcut.setTitleCondensed(mContext.getString(R.string.menu_bookmark));
        }
    }

    /**
     * Updates the price-tracking menu item visibility.
     *
     * @param startPriceTrackingMenuItem The menu item to start price tracking.
     * @param stopPriceTrackingMenuItem The menu item to stop price tracking.
     * @param currentTab Current tab being displayed.
     */
    protected void updatePriceTrackingMenuItemRow(
            @NonNull MenuItem startPriceTrackingMenuItem,
            @NonNull MenuItem stopPriceTrackingMenuItem,
            @Nullable Tab currentTab) {
        if (currentTab == null || currentTab.getWebContents() == null) {
            startPriceTrackingMenuItem.setVisible(false);
            stopPriceTrackingMenuItem.setVisible(false);
            return;
        }

        Profile profile = currentTab.getProfile();
        ShoppingService service = ShoppingServiceFactory.getForProfile(profile);
        ShoppingService.ProductInfo info = null;
        if (service != null) {
            info = service.getAvailableProductInfoForUrl(currentTab.getUrl());
        }

        // If price tracking isn't enabled or the page isn't eligible, then hide both items.
        if (!ShoppingFeatures.isShoppingListEligible(profile)
                || !PowerBookmarkUtils.isPriceTrackingEligible(currentTab)
                || !mBookmarkModelSupplier.hasValue()) {
            startPriceTrackingMenuItem.setVisible(false);
            stopPriceTrackingMenuItem.setVisible(false);
            return;
        }

        boolean editEnabled = mBookmarkModelSupplier.get().isEditBookmarksEnabled();
        startPriceTrackingMenuItem.setEnabled(editEnabled);
        stopPriceTrackingMenuItem.setEnabled(editEnabled);

        if (info != null && info.productClusterId.isPresent()) {
            CommerceSubscription sub =
                    new CommerceSubscription(
                            SubscriptionType.PRICE_TRACK,
                            IdentifierType.PRODUCT_CLUSTER_ID,
                            UnsignedLongs.toString(info.productClusterId.get()),
                            ManagementType.USER_MANAGED,
                            null);
            boolean isSubscribed = service.isSubscribedFromCache(sub);
            startPriceTrackingMenuItem.setVisible(!isSubscribed);
            stopPriceTrackingMenuItem.setVisible(isSubscribed);
        } else {
            startPriceTrackingMenuItem.setVisible(true);
            stopPriceTrackingMenuItem.setVisible(false);
        }
    }

    /**
     * Updates the find in page menu item's state.
     *
     * @param menu {@link Menu} for find in page.
     * @param currentTab Current tab being displayed.
     */
    private void updateFindInPageMenuItem(Menu menu, @Nullable Tab currentTab) {
        MenuItem findInPageMenuRow = menu.findItem(R.id.find_in_page_id);
        // PDF native page should show find in page menu item.
        boolean itemVisible =
                currentTab != null
                        && (shouldShowWebContentsDependentMenuItem(currentTab)
                                || (currentTab.isNativePage()
                                        && currentTab.getNativePage().isPdf()));
        findInPageMenuRow.setVisible(itemVisible);
    }

    /**
     * Updates the request desktop site item's state.
     *
     * @param menu {@link Menu} for request desktop site.
     * @param currentTab Current tab being displayed.
     * @param canShowRequestDesktopSite If the request desktop site menu item should show or not.
     * @param isNativePage Whether the current tab is a native page.
     */
    protected void updateRequestDesktopSiteMenuItem(
            Menu menu,
            @Nullable Tab currentTab,
            boolean canShowRequestDesktopSite,
            boolean isNativePage) {
        MenuItem requestMenuRow = menu.findItem(R.id.request_desktop_site_row_menu_id);
        MenuItem requestMenuLabel = menu.findItem(R.id.request_desktop_site_id);
        MenuItem requestMenuCheck = menu.findItem(R.id.request_desktop_site_check_id);

        // Hide request desktop site on all native pages. Also hide it for desktop Android, which
        // always requests desktop sites.
        boolean itemVisible =
                currentTab != null
                        && canShowRequestDesktopSite
                        && !isNativePage
                        && !shouldShowReaderModePrefs(currentTab)
                        && currentTab.getWebContents() != null
                        && !BuildConfig.IS_DESKTOP_ANDROID;

        requestMenuRow.setVisible(itemVisible);
        if (!itemVisible) return;

        boolean isRequestDesktopSite =
                currentTab.getWebContents().getNavigationController().getUseDesktopUserAgent();
        requestMenuLabel.setTitle(R.string.menu_request_desktop_site);
        requestMenuCheck.setVisible(true);
        // Mark the checkbox if RDS is activated on this page.
        requestMenuCheck.setChecked(isRequestDesktopSite);

        // This title doesn't seem to be displayed by Android, but it is used to set up
        // accessibility text in {@link AppMenuAdapter#setupMenuButton}.
        requestMenuLabel.setTitleCondensed(
                isRequestDesktopSite
                        ? mContext.getString(R.string.menu_request_desktop_site_on)
                        : mContext.getString(R.string.menu_request_desktop_site_off));
    }

    /**
     * Updates the auto dark menu item's state.
     *
     * @param menu {@link Menu} for auto dark.
     * @param currentTab Current tab being displayed.
     * @param isNativePage Whether the current tab is a native page.
     */
    protected void updateAutoDarkMenuItem(
            Menu menu, @Nullable Tab currentTab, boolean isNativePage) {
        MenuItem autoDarkMenuRow = menu.findItem(R.id.auto_dark_web_contents_row_menu_id);
        MenuItem autoDarkMenuCheck = menu.findItem(R.id.auto_dark_web_contents_check_id);

        // Hide app menu item if on non-NTP chrome:// page or auto dark not enabled.
        boolean isAutoDarkEnabled = isAutoDarkWebContentsEnabled();
        boolean itemVisible = currentTab != null && !isNativePage && isAutoDarkEnabled;
        autoDarkMenuRow.setVisible(itemVisible);
        if (!itemVisible) return;

        // Set text based on if site is blocked or not.
        boolean isEnabled =
                WebContentsDarkModeController.isEnabledForUrl(
                        mTabModelSelector.getCurrentModel().getProfile(), currentTab.getUrl());
        autoDarkMenuCheck.setChecked(isEnabled);
    }

    protected void updateManagedByMenuItem(Menu menu, @Nullable Tab currentTab) {
        MenuItem managedByDividerLine = menu.findItem(R.id.managed_by_divider_line_id);
        MenuItem managedByMenuItem = menu.findItem(R.id.managed_by_menu_id);

        boolean managedByMenuItemVisible =
                currentTab != null && shouldShowManagedByMenuItem(currentTab);

        managedByDividerLine.setVisible(managedByMenuItemVisible);
        managedByMenuItem.setVisible(managedByMenuItemVisible);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public boolean isIncognitoEnabled() {
        return IncognitoUtils.isIncognitoModeEnabled(
                mTabModelSelector.getCurrentModel().getProfile());
    }

    static void setPageBookmarkedForTesting(Boolean bookmarked) {
        sItemBookmarkedForTesting = bookmarked;
        ResettersForTesting.register(() -> sItemBookmarkedForTesting = null);
    }

    void setBookmarkModelSupplierForTesting(
            ObservableSupplier<BookmarkModel> bookmarkModelSupplier) {
        mBookmarkModelSupplier = bookmarkModelSupplier;
    }

    /**
     * @return Whether the menu item's icon need to be tinted to blue.
     */
    protected @ColorRes int getMenuItemIconColorRes(MenuItem menuItem) {
        final int itemId = menuItem.getItemId();
        if (itemId == R.id.disable_price_tracking_menu_id) {
            return R.color.default_icon_color_accent1_tint_list;
        }
        return R.color.default_icon_color_secondary_tint_list;
    }

    /**
     * Set the icon and the title for the menu item used for direct share.
     * @param item The menu item that is used for direct share.
     */
    protected void updateDirectShareMenuItem(MenuItem item) {
        Pair<Drawable, CharSequence> directShare = ShareHelper.getShareableIconAndNameForText();
        Drawable directShareIcon = directShare.first;
        CharSequence directShareTitle = directShare.second;

        item.setIcon(directShareIcon);
        if (directShareTitle != null) {
            item.setTitle(
                    mContext.getString(R.string.accessibility_menu_share_via, directShareTitle));
        }
    }

    /** Records user clicking on the menu button in New tab page. */
    @Override
    public void onMenuShown() {
        Tab currentTab = mActivityTabProvider.get();
        if (currentTab != null
                && UrlUtilities.isNtpUrl(currentTab.getUrl())
                && !currentTab.isIncognito()) {
            BrowserUiUtils.recordModuleClickHistogram(ModuleTypeOnStartAndNtp.MENU_BUTTON);
        }
    }
}