blob: 706618134a8bbf8cf88ad23ed52ee17ce444c2c1 [file] [log] [blame]
// 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.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.IdRes;
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 org.chromium.base.CallbackController;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.banners.AppMenuVerbiage;
import org.chromium.chrome.browser.bookmarks.BookmarkFeatures;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.PowerBookmarkUtils;
import org.chromium.chrome.browser.bookmarks.ReadingListFeatures;
import org.chromium.chrome.browser.commerce.ShoppingFeatures;
import org.chromium.chrome.browser.commerce.ShoppingServiceFactory;
import org.chromium.chrome.browser.device.DeviceClassManager;
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.flags.ChromeSwitches;
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.partnercustomizations.PartnerBrowserCustomizations;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.read_later.ReadingListUtils;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.share.ShareUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
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.webapps.WebappRegistry;
import org.chromium.chrome.features.start_surface.StartSurface;
import org.chromium.chrome.features.start_surface.StartSurfaceState;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkType;
import org.chromium.components.browser_ui.accessibility.PageZoomCoordinator;
import org.chromium.components.commerce.core.ShoppingService;
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.power_bookmarks.PowerBookmarkMeta;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.components.webapps.AppBannerManager;
import org.chromium.components.webapps.WebappsUtils;
import org.chromium.content_public.browser.ContentFeatureList;
import org.chromium.net.ConnectionType;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.modelutil.MVCListAdapter;
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.List;
import java.util.Map;
/**
* 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;
private static Boolean sItemInReadingListForTesting;
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;
// Keeps track of which menu item was shown when installable app is detected.
private int mAddAppTitleShown;
private Map<CustomViewBinder, Integer> mCustomViewTypeOffsetMap;
private boolean mIsTypeSpecificBookmarkItemRowPresent;
/**
* 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;
private @Nullable OneshotSupplier<StartSurface> mStartSurfaceSupplier;
private @Nullable StartSurface.StateObserver mStartSurfaceStateObserver;
private @StartSurfaceState int mStartSurfaceState;
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 startSurfaceSupplier An {@link OneshotSupplier} for the Start surface.
* @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,
@Nullable OneshotSupplier<StartSurface> startSurfaceSupplier,
ObservableSupplier<BookmarkModel> bookmarkModelSupplier,
@Nullable OneshotSupplier<IncognitoReauthController>
incognitoReauthControllerOneshotSupplier) {
mContext = context;
mIsTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext);
mActivityTabProvider = activityTabProvider;
mMultiWindowModeStateDispatcher = multiWindowModeStateDispatcher;
mTabModelSelector = tabModelSelector;
mToolbarManager = toolbarManager;
mDecorView = decorView;
if (incognitoReauthControllerOneshotSupplier != null) {
incognitoReauthControllerOneshotSupplier.onAvailable(
mIncognitoReauthCallbackController.makeCancelable(incognitoReauthController -> {
mIncognitoReauthController = incognitoReauthController;
}));
}
if (layoutStateProvidersSupplier != null) {
layoutStateProvidersSupplier.onAvailable(mCallbackController.makeCancelable(
layoutStateProvider -> { mLayoutStateProvider = layoutStateProvider; }));
}
if (!ReturnToChromeUtil.isStartSurfaceRefactorEnabled(mContext)
&& startSurfaceSupplier != null
&& ReturnToChromeUtil.isStartSurfaceEnabled(mContext)) {
mStartSurfaceSupplier = startSurfaceSupplier;
startSurfaceSupplier.onAvailable(mCallbackController.makeCancelable((startSurface) -> {
mStartSurfaceState = startSurface.getStartSurfaceState();
mStartSurfaceStateObserver = (newState, shouldShowToolbar) -> {
assert ReturnToChromeUtil.isStartSurfaceEnabled(mContext);
mStartSurfaceState = newState;
};
// TODO(https://crbug.com/1315679): Remove |mStartSurfaceSupplier|,
// |mStartSurfaceState| and |mStartSurfaceStateObserver| after the refactor is
// enabled by default.
startSurface.addStateChangeObserver(mStartSurfaceStateObserver);
}));
}
mBookmarkModelSupplier = bookmarkModelSupplier;
mShareUtils = new ShareUtils();
}
@Override
public void destroy() {
if (mCallbackController != null) {
mCallbackController.destroy();
mCallbackController = null;
}
if (mStartSurfaceSupplier != null) {
if (mStartSurfaceSupplier.get() != null) {
mStartSurfaceSupplier.get().removeStateChangeObserver(mStartSurfaceStateObserver);
}
mStartSurfaceSupplier = null;
mStartSurfaceStateObserver = null;
}
}
/**
* @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());
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)
&& !isInStartSurfaceHomepage();
}
/**
* @return Whether the Start surface homepage is showing.
*/
@VisibleForTesting
boolean isInStartSurfaceHomepage() {
if (ReturnToChromeUtil.isStartSurfaceRefactorEnabled(mContext)) {
return mLayoutStateProvider != null
&& mLayoutStateProvider.isLayoutVisible(LayoutType.START_SURFACE);
}
return mStartSurfaceSupplier != null && mStartSurfaceSupplier.get() != null
&& mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE;
}
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) {
ModelList modelList = new ModelList();
PopupMenu popup = new PopupMenu(mContext, mDecorView);
Menu menu = popup.getMenu();
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(getAppMenuLayoutId(), menu);
prepareMenu(menu, handler);
// TODO(crbug.com/1119550): Programmatically create menu item's PropertyModel instead of
// converting from MenuItems.
for (int i = 0; i < menu.size(); ++i) {
MenuItem item = menu.getItem(i);
if (!item.isVisible()) continue;
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());
if (item.hasSubMenu()) {
// Only support top level menu items have SUBMENU, and a SUBMENU item cannot have a
// SUBMENU.
// TODO(crbug.com/1183234) : 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));
}
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, isInStartSurfaceHomepage() ? null : 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 isChromeScheme = url.getScheme().equals(UrlConstants.CHROME_SCHEME)
|| url.getScheme().equals(UrlConstants.CHROME_NATIVE_SCHEME);
final boolean isFileScheme = url.getScheme().equals(UrlConstants.FILE_SCHEME);
final boolean isContentScheme = url.getScheme().equals(UrlConstants.CONTENT_SCHEME);
final boolean isHttpOrHttpsScheme = UrlUtilities.isHttpOrHttps(url);
// 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().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()));
}
updateBookmarkMenuItemRow(menu.findItem(R.id.add_bookmark_menu_id),
menu.findItem(R.id.edit_bookmark_menu_id), currentTab);
// Updates the type-specific bookmark menu item rows.
// The order listed here is reflects the relative priority. Subsequent updates should check
// the `mIsTypeSpecificBookmarkItemRowPresent` bit before updating their row.
mIsTypeSpecificBookmarkItemRowPresent = false;
updatePriceTrackingMenuItemRow(menu.findItem(R.id.enable_price_tracking_menu_id),
menu.findItem(R.id.disable_price_tracking_menu_id), currentTab);
updateReadingListMenuItemRow(menu.findItem(R.id.add_to_reading_list_menu_id),
menu.findItem(R.id.delete_from_reading_list_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) {
ShareHelper.configureDirectShareMenuItem(
mContext, menu.findItem(R.id.direct_share_menu_id));
}
menu.findItem(R.id.paint_preview_show_id)
.setVisible(isCurrentTabNotNull
&& shouldShowPaintPreview(isChromeScheme, currentTab, isIncognito));
// Enable image descriptions if touch exploration is currently enabled, but not on the
// native NTP or Start surface.
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 = Profile.getLastUsedRegularProfile();
// 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 or on Start surface.
menu.findItem(R.id.page_zoom_id)
.setVisible(isCurrentTabNotNull
&& shouldShowWebContentsDependentMenuItem(currentTab)
&& PageZoomCoordinator.shouldShowMenuItem());
// Disable find in page on the native NTP or on Start surface.
menu.findItem(R.id.find_in_page_id)
.setVisible(
isCurrentTabNotNull && shouldShowWebContentsDependentMenuItem(currentTab));
// Prepare translate menu button.
prepareTranslateMenuItem(menu, currentTab);
prepareAddToHomescreenMenuItem(menu, currentTab,
shouldShowHomeScreenMenuItem(
isChromeScheme, isFileScheme, isContentScheme, isIncognito, url));
updateRequestDesktopSiteMenuItem(menu, currentTab, true /* can show */, isChromeScheme);
updateAutoDarkMenuItem(menu, currentTab, isChromeScheme);
// 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));
// Only display the Enter VR button if VR Shell Dev environment is enabled.
menu.findItem(R.id.enter_vr_id).setVisible(isCurrentTabNotNull && shouldShowEnterVr());
updateManagedByMenuItem(menu, currentTab);
}
/**
* @return The number of Chrome instances either running alive or dormant but the state
* is present for restoration.
*/
private 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 isTabSelectionEditorContext = isOverviewModeMenu
&& TabUiFeatureUtilities.isTabGroupsAndroidEnabled(mContext)
&& !DeviceClassManager.enableAccessibilityLayout(mContext);
boolean isMenuSelectTabsEnabled = false;
boolean isMenuSelectTabsVisible = false;
boolean isMenuGroupTabsEnabled = false;
boolean isMenuGroupTabsVisible = false;
if (TabUiFeatureUtilities.isTabSelectionEditorV2Enabled(mContext)) {
isMenuSelectTabsVisible = isTabSelectionEditorContext;
isMenuSelectTabsEnabled = !isIncognitoReauthShowing && isMenuSelectTabsVisible
&& mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.getCount()
!= 0;
} else {
isMenuGroupTabsVisible = isTabSelectionEditorContext;
isMenuGroupTabsEnabled = !isIncognitoReauthShowing && isMenuGroupTabsVisible
&& mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.getTabsWithNoOtherRelatedTabs()
.size()
> 1;
}
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_group_tabs) {
item.setVisible(isMenuGroupTabsVisible);
item.setEnabled(isMenuGroupTabsEnabled);
}
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);
}
// 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 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);
}
/**
* @param currentTab The currentTab for which the app menu is showing.
* @return Whether reading list menu item should be highlighted, indicating that the current tab
* exists in the reading list.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public boolean shouldHighlightReadingList(@NonNull Tab currentTab) {
if (sItemInReadingListForTesting != null) return sItemInReadingListForTesting;
if (!mBookmarkModelSupplier.hasValue()) return false;
BookmarkId existingBookmark =
mBookmarkModelSupplier.get().getUserBookmarkIdForTab(currentTab);
return existingBookmark != null && existingBookmark.getType() == BookmarkType.READING_LIST;
}
/**
* @param currentTab The currentTab for which the app menu is showing.
* @return Whether price tracking is enabled and the button should be highlighted.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public boolean shouldHighlightPriceTracking(@NonNull Tab currentTab) {
// TODO(crbug.com/1266624): Read this information from power bookmarks when available.
return false;
}
/**
* @return Whether the update Chrome menu item should be displayed.
*/
protected boolean shouldShowUpdateMenuItem() {
return UpdateMenuItemHelper.getInstance().getUiState().itemState != null;
}
/**
* @return Whether the "Move to other window" menu item should be displayed.
*/
protected boolean shouldShowMoveToOtherWindow() {
if (!instanceSwitcherEnabled() && shouldShowNewWindow()) return false;
boolean hasMoreThanOneTab = mTabModelSelector.getTotalTabCount() > 1;
boolean showAlsoForSingleTab = !isPartnerHomepageEnabled();
if (!hasMoreThanOneTab && !showAlsoForSingleTab) return false;
if (instanceSwitcherEnabled()) {
// Moving tabs should be possible to any other instance.
return getInstanceCount() > 1;
} else {
return mMultiWindowModeStateDispatcher.isOpenInOtherWindowSupported();
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public boolean instanceSwitcherEnabled() {
return MultiWindowUtils.instanceSwitcherEnabled()
&& MultiWindowUtils.isMultiInstanceApi31Enabled();
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public boolean isTabletSizeScreen() {
return mIsTablet;
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public boolean isPartnerHomepageEnabled() {
return PartnerBrowserCustomizations.getInstance().isHomepageProviderAvailableAndEnabled();
}
@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() {
if (instanceSwitcherEnabled()) {
// 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 isChromeScheme Whether URL for the current tab starts with the chrome:// scheme.
* @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 isChromeScheme, @NonNull Tab currentTab, boolean isIncognito) {
return ChromeFeatureList.sPaintPreviewDemo.isEnabled() && !isChromeScheme && !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 the Start service or native NTP, and true otherwise.
*/
protected boolean shouldShowWebContentsDependentMenuItem(@NonNull Tab currentTab) {
return !currentTab.isNativePage() && currentTab.getWebContents() != null;
}
/**
* @return Whether the enter VR menu item should be displayed.
*/
protected boolean shouldShowEnterVr() {
return CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_VR_SHELL_DEV);
}
/**
* 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 isChromeScheme Whether URL for the current tab starts with the chrome:// scheme.
* @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 isChromeScheme, boolean isFileScheme,
boolean isContentScheme, boolean isIncognito, @NonNull GURL url) {
// Hide 'Add to homescreen' for the following:
// * chrome:// 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() && !isChromeScheme && !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 homescreenItem = menu.findItem(R.id.add_to_homescreen_id);
MenuItem openWebApkItem = menu.findItem(R.id.open_webapk_id);
mAddAppTitleShown = AppMenuVerbiage.APP_MENU_OPTION_UNKNOWN;
if (currentTab != null && shouldShowHomeScreenMenuItem) {
Context context = ContextUtils.getApplicationContext();
long addToHomeScreenStart = SystemClock.elapsedRealtime();
ResolveInfo resolveInfo = queryWebApkResolvInfo(context, currentTab);
RecordHistogram.recordTimesHistogram("Android.PrepareMenu.OpenWebApkVisibilityCheck",
SystemClock.elapsedRealtime() - addToHomeScreenStart);
boolean openWebApkItemVisible =
resolveInfo != null && resolveInfo.activityInfo.packageName != null;
if (openWebApkItemVisible) {
String appName = resolveInfo.loadLabel(context.getPackageManager()).toString();
openWebApkItem.setTitle(context.getString(R.string.menu_open_webapk, appName));
homescreenItem.setVisible(false);
openWebApkItem.setVisible(true);
} else {
AppBannerManager.InstallStringPair installStrings =
getAddToHomeScreenTitle(currentTab);
homescreenItem.setTitle(installStrings.titleTextId);
homescreenItem.setVisible(true);
openWebApkItem.setVisible(false);
if (installStrings.titleTextId == AppBannerManager.NON_PWA_PAIR.titleTextId) {
mAddAppTitleShown = AppMenuVerbiage.APP_MENU_OPTION_ADD_TO_HOMESCREEN;
} else if (installStrings.titleTextId == AppBannerManager.PWA_PAIR.titleTextId) {
mAddAppTitleShown = AppMenuVerbiage.APP_MENU_OPTION_INSTALL;
}
}
} else {
homescreenItem.setVisible(false);
openWebApkItem.setVisible(false);
}
}
private ResolveInfo queryWebApkResolvInfo(Context context, Tab currentTab) {
ResolveInfo resolveInfo = null;
if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_APK_UNIQUE_ID)) {
String manifestId = AppBannerManager.maybeGetManifestId(currentTab.getWebContents());
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;
}
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public AppBannerManager.InstallStringPair getAddToHomeScreenTitle(@NonNull Tab currentTab) {
return AppBannerManager.getHomescreenLanguageOption(currentTab.getWebContents());
}
@Override
public Bundle getBundleForMenuItem(int itemId) {
Bundle bundle = new Bundle();
if (itemId == R.id.add_to_homescreen_id) {
bundle.putInt(AppBannerManager.MENU_TITLE_KEY, mAddAppTitleShown);
}
return bundle;
}
/**
* 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);
}
@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.getInstance().onMenuDismissed();
UpdateMenuItemHelper.getInstance().unregisterObserver(mAppMenuInvalidator);
mUpdateMenuItemVisible = false;
mAppMenuInvalidator = null;
}
}
@VisibleForTesting
boolean shouldShowIconRow() {
boolean shouldShowIconRow = mIsTablet ? mDecorView.getWidth()
< DeviceFormFactor.getNonMultiDisplayMinimumTabletWidthPx(mContext)
: !isInStartSurfaceHomepage();
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;
}
private int getUmaEnumForMenuItem(@Nullable @IdRes Integer menuItemId) {
if (menuItemId == null) return AppMenuHighlightItem.UNKNOWN;
if (menuItemId == R.id.downloads_menu_id) {
return AppMenuHighlightItem.DOWNLOADS;
} else if (menuItemId == R.id.all_bookmarks_menu_id) {
return AppMenuHighlightItem.BOOKMARKS;
} else if (menuItemId == R.id.translate_id) {
return AppMenuHighlightItem.TRANSLATE;
} else if (menuItemId == R.id.add_to_homescreen_id) {
return AppMenuHighlightItem.ADD_TO_HOMESCREEN;
} else if (menuItemId == R.id.offline_page_id) {
return AppMenuHighlightItem.DOWNLOAD_THIS_PAGE;
} else if (menuItemId == R.id.bookmark_this_page_id) {
return AppMenuHighlightItem.BOOKMARK_THIS_PAGE;
} else if (menuItemId == R.id.app_menu_footer) {
return AppMenuHighlightItem.DATA_REDUCTION_FOOTER;
}
return AppMenuHighlightItem.UNKNOWN;
}
/**
* 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.btn_star);
bookmarkMenuItemShortcut.setChecked(false);
bookmarkMenuItemShortcut.setTitleCondensed(mContext.getString(R.string.menu_bookmark));
}
}
/**
* Updates the bookmark item's visibility.
*
* @param bookmarkMenuItemAdd {@link MenuItem} for adding the bookmark.
* @param bookmarkMenuItemEdit {@link MenuItem} for editing the bookmark.
* @param currentTab Current tab being displayed.
*/
protected void updateBookmarkMenuItemRow(
MenuItem bookmarkMenuItemAdd, MenuItem bookmarkMenuItemEdit, @Nullable Tab currentTab) {
// If the bookmark menu item row is disabled, then hide both item.
if (!BookmarkFeatures.isBookmarkMenuItemAsDedicatedRowEnabled()
|| !mBookmarkModelSupplier.hasValue() || currentTab == null) {
bookmarkMenuItemAdd.setVisible(false);
bookmarkMenuItemEdit.setVisible(false);
return;
}
boolean editEnabled = mBookmarkModelSupplier.get().isEditBookmarksEnabled();
bookmarkMenuItemAdd.setEnabled(editEnabled);
bookmarkMenuItemEdit.setEnabled(editEnabled);
boolean shouldCheckBookmarkStar = currentTab != null && shouldCheckBookmarkStar(currentTab);
bookmarkMenuItemAdd.setVisible(!shouldCheckBookmarkStar);
bookmarkMenuItemEdit.setVisible(shouldCheckBookmarkStar);
}
/**
* Updates the bookmark item's visibility.
*
* @param readingListMenuItemAdd {@link MenuItem} for adding to the reading list.
* @param readingListMenuItemDelete {@link MenuItem} for deleting from the reading list.
* @param currentTab Current tab being displayed.
*/
protected void updateReadingListMenuItemRow(@NonNull MenuItem readingListMenuItemAdd,
@NonNull MenuItem readingListMenuItemDelete, @Nullable Tab currentTab) {
// If the reading list item row is disabled, then hide both items.
if (!ReadingListFeatures.isReadingListMenuItemAsDedicatedRowEnabled()
|| !mBookmarkModelSupplier.hasValue() || currentTab == null
|| !ReadingListUtils.isReadingListSupported(currentTab.getUrl())
|| mIsTypeSpecificBookmarkItemRowPresent) {
readingListMenuItemAdd.setVisible(false);
readingListMenuItemDelete.setVisible(false);
return;
}
mIsTypeSpecificBookmarkItemRowPresent = true;
boolean editEnabled = mBookmarkModelSupplier.get().isEditBookmarksEnabled();
readingListMenuItemAdd.setEnabled(editEnabled);
readingListMenuItemDelete.setEnabled(editEnabled);
boolean readingListItemExists = shouldHighlightReadingList(currentTab);
readingListMenuItemAdd.setVisible(!readingListItemExists);
readingListMenuItemDelete.setVisible(readingListItemExists);
}
/**
* 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) {
ShoppingService service =
ShoppingServiceFactory.getForProfile(Profile.getLastUsedRegularProfile());
ShoppingService.ProductInfo info = null;
if (service != null && currentTab != null) {
info = service.getAvailableProductInfoForUrl(currentTab.getUrl());
}
// If price tracking isn't enabled or the page isn't eligible, then hide both items.
if (!ShoppingFeatures.isShoppingListEnabled()
|| !PowerBookmarkUtils.isPriceTrackingEligible(currentTab)
|| mIsTypeSpecificBookmarkItemRowPresent || !mBookmarkModelSupplier.hasValue()) {
startPriceTrackingMenuItem.setVisible(false);
stopPriceTrackingMenuItem.setVisible(false);
return;
}
PowerBookmarkMeta existingBookmarkMeta = PowerBookmarkUtils.getBookmarkBookmarkMetaForTab(
mBookmarkModelSupplier.get(), currentTab);
if (existingBookmarkMeta != null && !existingBookmarkMeta.hasShoppingSpecifics()) {
startPriceTrackingMenuItem.setVisible(false);
stopPriceTrackingMenuItem.setVisible(false);
return;
}
mIsTypeSpecificBookmarkItemRowPresent = true;
boolean editEnabled = mBookmarkModelSupplier.get().isEditBookmarksEnabled();
startPriceTrackingMenuItem.setEnabled(editEnabled);
stopPriceTrackingMenuItem.setEnabled(editEnabled);
boolean priceTrackingEnabled = false;
if (info != null) {
priceTrackingEnabled = PowerBookmarkUtils.isPriceTrackingEnabledForClusterId(
info.productClusterId, mBookmarkModelSupplier.get());
}
startPriceTrackingMenuItem.setVisible(!priceTrackingEnabled);
stopPriceTrackingMenuItem.setVisible(priceTrackingEnabled);
}
/**
* 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 isChromeScheme Whether URL for the current tab starts with the chrome:// scheme.
*/
protected void updateRequestDesktopSiteMenuItem(Menu menu, @Nullable Tab currentTab,
boolean canShowRequestDesktopSite, boolean isChromeScheme) {
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 chrome:// pages except for the NTP. If
// REQUEST_DESKTOP_SITE_EXCEPTIONS is enabled, hide the entry for all native pages.
boolean itemVisible = currentTab != null && canShowRequestDesktopSite
&& (!isChromeScheme
|| (!ContentFeatureList.isEnabled(
ContentFeatureList.REQUEST_DESKTOP_SITE_EXCEPTIONS)
&& currentTab.isNativePage()))
&& !shouldShowReaderModePrefs(currentTab) && currentTab.getWebContents() != null;
requestMenuRow.setVisible(itemVisible);
if (!itemVisible) return;
boolean isRequestDesktopSite =
currentTab.getWebContents().getNavigationController().getUseDesktopUserAgent();
if (ChromeFeatureList.sAppMenuMobileSiteOption.isEnabled()) {
requestMenuLabel.setTitle(isRequestDesktopSite
? R.string.menu_item_request_mobile_site
: R.string.menu_item_request_desktop_site);
requestMenuLabel.setIcon(isRequestDesktopSite ? R.drawable.smartphone_black_24dp
: R.drawable.ic_desktop_windows);
requestMenuCheck.setVisible(false);
} else {
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 isChromeScheme Whether URL for the current tab starts with the chrome:// scheme.
*/
protected void updateAutoDarkMenuItem(
Menu menu, @Nullable Tab currentTab, boolean isChromeScheme) {
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 && !isChromeScheme && 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();
}
@VisibleForTesting
static void setPageBookmarkedForTesting(Boolean bookmarked) {
sItemBookmarkedForTesting = bookmarked;
}
@VisibleForTesting
static void setPageInReadingListForTesting(Boolean highlight) {
sItemInReadingListForTesting = highlight;
}
@VisibleForTesting
void setStartSurfaceStateForTesting(@StartSurfaceState int state) {
mStartSurfaceState = state;
}
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.edit_bookmark_menu_id || itemId == R.id.delete_from_reading_list_menu_id
|| 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;
}
}