blob: 802c469af820391410795fd5ace75c75f14d0647 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
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.Callback;
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.ShortcutHelper;
import org.chromium.chrome.browser.banners.AppBannerManager;
import org.chromium.chrome.browser.banners.AppMenuVerbiage;
import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.flags.CachedFeatureFlags;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.flags.StringCachedFieldTrialParameter;
import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController;
import org.chromium.chrome.browser.incognito.IncognitoUtils;
import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
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.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.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.appmenu.CustomViewBinder;
import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration;
import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.ui.base.DeviceFormFactor;
import java.util.ArrayList;
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 {
public static final StringCachedFieldTrialParameter ACTION_BAR_VARIATION =
new StringCachedFieldTrialParameter(
ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, "action_bar", "");
public static final StringCachedFieldTrialParameter THREE_BUTTON_ACTION_BAR_VARIATION =
new StringCachedFieldTrialParameter(
ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR,
"three_button_action_bar", "");
private static Boolean sItemBookmarkedForTesting;
protected MenuItem mReloadMenuItem;
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 mCallbackController = new CallbackController();
private final ObservableSupplier<BookmarkBridge> mBookmarkBridgeSupplier;
private Callback<BookmarkBridge> mBookmarkBridgeSupplierCallback;
private boolean mUpdateMenuItemVisible;
private ShareUtils mShareUtils;
// Keeps track of which menu item was shown when installable app is detected.
private int mAddAppTitleShown;
@VisibleForTesting
@IntDef({MenuGroup.INVALID, MenuGroup.PAGE_MENU, MenuGroup.OVERVIEW_MODE_MENU,
MenuGroup.START_SURFACE_MODE_MENU, MenuGroup.TABLET_EMPTY_MODE_MENU})
@interface MenuGroup {
int INVALID = -1;
int PAGE_MENU = 0;
int OVERVIEW_MODE_MENU = 1;
int START_SURFACE_MODE_MENU = 2;
int TABLET_EMPTY_MODE_MENU = 3;
}
@IntDef({ActionBarType.STANDARD, ActionBarType.BACKWARD_BUTTON, ActionBarType.SHARE_BUTTON})
@interface ActionBarType {
int STANDARD = 0;
int BACKWARD_BUTTON = 1;
int SHARE_BUTTON = 2;
}
@IntDef({ThreeButtonActionBarType.DISABLED, ThreeButtonActionBarType.ACTION_CHIP_VIEW,
ThreeButtonActionBarType.DESTINATION_CHIP_VIEW})
@interface ThreeButtonActionBarType {
int DISABLED = 0;
int ACTION_CHIP_VIEW = 1;
int DESTINATION_CHIP_VIEW = 2;
}
protected @Nullable OverviewModeBehavior mOverviewModeBehavior;
protected BookmarkBridge mBookmarkBridge;
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 overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
* @param bookmarkBridgeSupplier An {@link ObservableSupplier} for the {@link BookmarkBridge}
* associated with the containing activity.
*/
public AppMenuPropertiesDelegateImpl(Context context, ActivityTabProvider activityTabProvider,
MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView,
@Nullable OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier) {
mContext = context;
mIsTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext);
mActivityTabProvider = activityTabProvider;
mMultiWindowModeStateDispatcher = multiWindowModeStateDispatcher;
mTabModelSelector = tabModelSelector;
mToolbarManager = toolbarManager;
mDecorView = decorView;
if (overviewModeBehaviorSupplier != null) {
overviewModeBehaviorSupplier.onAvailable(mCallbackController.makeCancelable(
overviewModeBehavior -> { mOverviewModeBehavior = overviewModeBehavior; }));
}
mBookmarkBridgeSupplier = bookmarkBridgeSupplier;
mBookmarkBridgeSupplierCallback = (bookmarkBridge) -> mBookmarkBridge = bookmarkBridge;
mBookmarkBridgeSupplier.addObserver(mBookmarkBridgeSupplierCallback);
mShareUtils = new ShareUtils();
}
@Override
public void destroy() {
mBookmarkBridgeSupplier.removeObserver(mBookmarkBridgeSupplierCallback);
if (mCallbackController != null) {
mCallbackController.destroy();
mCallbackController = null;
}
}
@Override
public int getAppMenuLayoutId() {
if (shouldShowRegroupedMenu() || shouldShowThreeButtonActionBar()) {
return R.menu.main_menu_regroup;
}
return R.menu.main_menu;
}
@Override
public @Nullable List<CustomViewBinder> getCustomViewBinders() {
List<CustomViewBinder> customViewBinders = new ArrayList<>();
customViewBinders.add(new UpdateMenuItemViewBinder());
customViewBinders.add(new ManagedByMenuItemViewBinder());
customViewBinders.add(new IncognitoMenuItemViewBinder());
customViewBinders.add(new DividerLineMenuItemViewBinder());
customViewBinders.add(new ChipViewMenuItemViewBinder(getThreeButtonActionBarType()));
return customViewBinders;
}
/**
* @return Whether the app menu for a web page should be shown.
*/
protected boolean shouldShowPageMenu() {
boolean isOverview =
mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible();
if (mIsTablet) {
boolean hasTabs = mTabModelSelector.getCurrentModel().getCount() != 0;
return hasTabs && !isOverview;
} else {
return !isOverview && mActivityTabProvider.get() != null;
}
}
@VisibleForTesting
@MenuGroup
int getMenuGroup() {
// Determine which menu to show.
@MenuGroup
int menuGroup = MenuGroup.INVALID;
if (shouldShowPageMenu()) menuGroup = MenuGroup.PAGE_MENU;
boolean isOverview =
mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible();
if (mIsTablet) {
boolean hasTabs = mTabModelSelector.getCurrentModel().getCount() != 0;
if (hasTabs && isOverview) {
menuGroup = MenuGroup.OVERVIEW_MODE_MENU;
} else if (!hasTabs) {
menuGroup = MenuGroup.TABLET_EMPTY_MODE_MENU;
}
} else if (isOverview) {
menuGroup = StartSurfaceConfiguration.isStartSurfaceEnabled()
? MenuGroup.START_SURFACE_MODE_MENU
: MenuGroup.OVERVIEW_MODE_MENU;
}
assert menuGroup != MenuGroup.INVALID;
return menuGroup;
}
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.START_SURFACE_MODE_MENU, menuGroup == MenuGroup.START_SURFACE_MODE_MENU);
menu.setGroupVisible(
R.id.TABLET_EMPTY_MODE_MENU, menuGroup == MenuGroup.TABLET_EMPTY_MODE_MENU);
}
@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 && currentTab != null) {
preparePageMenu(menu, currentTab, handler, isIncognito);
}
prepareCommonMenuItems(menu, menuGroup, isIncognito);
}
private void preparePageMenu(
Menu menu, Tab currentTab, AppMenuHandler handler, boolean isIncognito) {
String url = currentTab.getUrlString();
boolean isChromeScheme = url.startsWith(UrlConstants.CHROME_URL_PREFIX)
|| url.startsWith(UrlConstants.CHROME_NATIVE_URL_PREFIX);
boolean isFileScheme = url.startsWith(UrlConstants.FILE_URL_PREFIX);
boolean isContentScheme = url.startsWith(UrlConstants.CONTENT_URL_PREFIX);
// 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();
@ActionBarType
int actionBarType = getActionBarType();
MenuItem backwardMenuItem = actionBar.findItem(R.id.backward_menu_id);
if (backwardMenuItem != null) {
if (actionBarType == ActionBarType.BACKWARD_BUTTON) {
backwardMenuItem.setEnabled(currentTab.canGoBack());
} else {
actionBar.removeItem(R.id.backward_menu_id);
}
}
// Disable the "Forward" menu item if there is no page to go to.
MenuItem forwardMenuItem = actionBar.findItem(R.id.forward_menu_id);
forwardMenuItem.setEnabled(currentTab.canGoForward());
mReloadMenuItem = actionBar.findItem(R.id.reload_menu_id);
Drawable icon = AppCompatResources.getDrawable(mContext, R.drawable.btn_reload_stop);
DrawableCompat.setTintList(icon,
AppCompatResources.getColorStateList(
mContext, R.color.default_icon_color_tint_list));
mReloadMenuItem.setIcon(icon);
loadingStateChanged(currentTab.isLoading());
MenuItem bookmarkMenuItem = actionBar.findItem(R.id.bookmark_this_page_id);
if (shouldShowThreeButtonActionBar()) {
actionBar.removeItem(R.id.bookmark_this_page_id);
} else {
updateBookmarkMenuItem(bookmarkMenuItem, currentTab);
}
MenuItem offlineMenuItem = actionBar.findItem(R.id.offline_page_id);
if (offlineMenuItem != null) {
if (shouldShowThreeButtonActionBar()) {
actionBar.removeItem(R.id.offline_page_id);
} else {
offlineMenuItem.setEnabled(shouldEnableDownloadPage(currentTab));
}
}
MenuItem shareMenuItem = actionBar.findItem(R.id.share_menu_button_id);
if (shareMenuItem != null) {
if (shouldShowShareInMenu()) {
actionBar.removeItem(R.id.share_menu_button_id);
} else {
shareMenuItem.setEnabled(mShareUtils.shouldEnableShare(currentTab));
}
}
if (shouldShowInfoInMenu()) {
actionBar.removeItem(R.id.info_menu_id);
}
if (shouldShowThreeButtonActionBar()) {
assert actionBar.size() == 3;
} else {
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.move_to_other_window_menu_id).setVisible(shouldShowMoveToOtherWindow());
if (shouldShowThreeButtonActionBar()) {
@ThreeButtonActionBarType
int threeButtonActionBarType = getThreeButtonActionBarType();
MenuItem downloadMenuItem =
menu.findItem(R.id.downloads_row_menu_id).getSubMenu().getItem(1);
assert downloadMenuItem.getItemId() == R.id.offline_page_chip_id;
downloadMenuItem.setEnabled(shouldEnableDownloadPage(currentTab));
MenuItem bookmarkMenuItem =
menu.findItem(R.id.all_bookmarks_row_menu_id).getSubMenu().getItem(1);
assert bookmarkMenuItem.getItemId() == R.id.bookmark_this_page_chip_id;
updateBookmarkMenuItem(bookmarkMenuItem, currentTab);
// Update titles for ChipView menu items.
if (threeButtonActionBarType == ThreeButtonActionBarType.ACTION_CHIP_VIEW) {
downloadMenuItem.setTitle(R.string.add);
if (bookmarkMenuItem.isChecked()) {
bookmarkMenuItem.setTitle(R.string.bookmark_item_edit);
} else {
bookmarkMenuItem.setTitle(R.string.add);
}
} else if (threeButtonActionBarType == ThreeButtonActionBarType.DESTINATION_CHIP_VIEW) {
MenuItem allDownloadMenuItem =
menu.findItem(R.id.downloads_row_menu_id).getSubMenu().getItem(0);
assert allDownloadMenuItem.getItemId() == R.id.downloads_menu_id;
allDownloadMenuItem.setTitle(R.string.all);
MenuItem allBookmarkMenuItem =
menu.findItem(R.id.all_bookmarks_row_menu_id).getSubMenu().getItem(0);
assert allBookmarkMenuItem.getItemId() == R.id.all_bookmarks_menu_id;
allBookmarkMenuItem.setTitle(R.string.all);
}
}
// Don't allow either "chrome://" pages or interstitial pages to be shared.
menu.findItem(R.id.share_row_menu_id)
.setVisible(mShareUtils.shouldEnableShare(currentTab) && shouldShowShareInMenu());
ShareHelper.configureDirectShareMenuItem(
mContext, menu.findItem(R.id.direct_share_menu_id));
menu.findItem(R.id.paint_preview_show_id)
.setVisible(shouldShowPaintPreview(isChromeScheme, currentTab, isIncognito));
// Enable image descriptions if the feature flag is enabled, and if a screen reader
// is currently running.
if (ImageDescriptionsController.getInstance().shouldShowImageDescriptionsMenuItem()) {
menu.findItem(R.id.get_image_descriptions_id).setVisible(true);
menu.findItem(R.id.get_image_descriptions_id)
.setTitle(ImageDescriptionsController.getInstance().imageDescriptionsEnabled()
? R.string.menu_stop_image_descriptions
: R.string.menu_get_image_descriptions);
} else {
menu.findItem(R.id.get_image_descriptions_id).setVisible(false);
}
// Disable find in page on the native NTP.
menu.findItem(R.id.find_in_page_id).setVisible(shouldShowFindInPage(currentTab));
// Prepare translate menu button.
prepareTranslateMenuItem(menu, currentTab);
prepareAddToHomescreenMenuItem(menu, currentTab,
shouldShowHomeScreenMenuItem(
isChromeScheme, isFileScheme, isContentScheme, isIncognito, url));
updateRequestDesktopSiteMenuItem(menu, currentTab, true /* can show */);
// Only display reader mode settings menu option if the current page is in reader mode.
menu.findItem(R.id.reader_mode_prefs_id).setVisible(shouldShowReaderModePrefs(currentTab));
MenuItem infoMenuItem = menu.findItem(R.id.info_id);
if (infoMenuItem != null) {
infoMenuItem.setVisible(shouldShowInfoInMenu());
}
// Only display the Enter VR button if VR Shell Dev environment is enabled.
menu.findItem(R.id.enter_vr_id).setVisible(shouldShowEnterVr());
MenuItem managedByMenuItem = menu.findItem(R.id.managed_by_menu_id);
managedByMenuItem.setVisible(shouldShowManagedByMenuItem(currentTab));
// TODO(https://crbug.com/1092175): Enable "managed by" menu item after chrome://management
// page is added.
managedByMenuItem.setEnabled(false);
}
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 isMenuGroupTabsVisible = TabUiFeatureUtilities.isTabGroupsAndroidEnabled()
&& !DeviceClassManager.enableAccessibilityLayout();
boolean isMenuGroupTabsEnabled = isMenuGroupTabsVisible
&& mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.getTabsWithNoOtherRelatedTabs()
.size()
> 1;
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);
}
}
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.
item.setEnabled(isIncognitoEnabled());
}
if (item.getItemId() == R.id.divider_line_id) {
item.setEnabled(false);
}
int itemGroupId = item.getGroupId();
if (!(menuGroup == MenuGroup.START_SURFACE_MODE_MENU
&& itemGroupId == R.id.START_SURFACE_MODE_MENU
|| 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.close_all_tabs_menu_id) {
boolean hasTabs = mTabModelSelector.getTotalTabCount() > 0;
item.setVisible(!isIncognito);
item.setEnabled(hasTabs);
}
if (item.getItemId() == R.id.close_all_incognito_tabs_menu_id) {
boolean hasIncognitoTabs = mTabModelSelector.getModel(true).getCount() > 0;
item.setVisible(isIncognito);
item.setEnabled(hasIncognitoTabs);
}
}
}
/**
* @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.getUrlString());
}
/**
* @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) {
return sItemBookmarkedForTesting != null
? sItemBookmarkedForTesting
: mBookmarkBridge != null && mBookmarkBridge.hasBookmarkIdForTab(currentTab);
}
/**
* @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() {
boolean hasMoreThanOneTab = mTabModelSelector.getTotalTabCount() > 1;
return mMultiWindowModeStateDispatcher.isOpenInOtherWindowSupported() && hasMoreThanOneTab;
}
/**
* @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 CachedFeatureFlags.isEnabled(ChromeFeatureList.PAINT_PREVIEW_DEMO) && !isChromeScheme
&& !isIncognito;
}
/**
* @param currentTab The currentTab for which the app menu is showing.
* @return Whether the find in page menu item should be displayed.
*/
protected boolean shouldShowFindInPage(@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);
}
/**
* @param currentTab The currentTab for which the app menu is showing.
* @return Whether the translate menu item should be displayed.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public boolean shouldShowTranslateMenuItem(@NonNull Tab currentTab) {
return TranslateUtils.canTranslateCurrentTab(currentTab);
}
/**
* @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, String 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 ShortcutHelper.isAddToHomeIntentSupported() && !isChromeScheme && !isFileScheme
&& !isContentScheme && !isIncognito && !TextUtils.isEmpty(url);
}
/**
* @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 (shouldShowHomeScreenMenuItem) {
Context context = ContextUtils.getApplicationContext();
long addToHomeScreenStart = SystemClock.elapsedRealtime();
ResolveInfo resolveInfo =
WebApkValidator.queryFirstWebApkResolveInfo(context, currentTab.getUrlString());
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);
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public AppBannerManager.InstallStringPair getAddToHomeScreenTitle(Tab currentTab) {
return AppBannerManager.getHomescreenLanguageOption(currentTab);
}
@Override
public Bundle getBundleForMenuItem(MenuItem item) {
Bundle bundle = new Bundle();
if (item.getItemId() == 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, Tab currentTab) {
boolean isTranslateVisible = shouldShowTranslateMenuItem(currentTab);
RecordHistogram.recordBooleanHistogram(
"Translate.MobileMenuTranslate.Shown", isTranslateVisible);
menu.findItem(R.id.translate_id).setVisible(isTranslateVisible);
}
@Override
public void loadingStateChanged(boolean isLoading) {
if (mReloadMenuItem != null) {
Resources resources = mContext.getResources();
mReloadMenuItem.getIcon().setLevel(isLoading
? resources.getInteger(R.integer.reload_button_level_stop)
: resources.getInteger(R.integer.reload_button_level_reload));
mReloadMenuItem.setTitle(isLoading ? R.string.accessibility_btn_stop_loading
: R.string.accessibility_btn_refresh);
mReloadMenuItem.setTitleCondensed(resources.getString(
isLoading ? R.string.menu_stop_refresh : R.string.menu_refresh));
}
}
@Override
public void onMenuDismissed() {
mReloadMenuItem = 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);
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;
}
/**
* Updates the bookmark item's visibility.
*
* @param bookmarkMenuItem {@link MenuItem} for adding/editing the bookmark.
* @param currentTab Current tab being displayed.
*/
protected void updateBookmarkMenuItem(MenuItem bookmarkMenuItem, Tab currentTab) {
// If this method is called before the {@link #mBookmarkBridgeSupplierCallback} has been
// called, try to retrieve the bridge directly from the supplier.
if (mBookmarkBridge == null && mBookmarkBridgeSupplier != null) {
mBookmarkBridge = mBookmarkBridgeSupplier.get();
}
if (mBookmarkBridge == null) {
// If the BookmarkBridge still isn't available, assume the bookmark menu item is not
// editable.
bookmarkMenuItem.setEnabled(false);
} else {
bookmarkMenuItem.setEnabled(mBookmarkBridge.isEditBookmarksEnabled());
}
if (shouldCheckBookmarkStar(currentTab)) {
bookmarkMenuItem.setIcon(R.drawable.btn_star_filled);
bookmarkMenuItem.setChecked(true);
bookmarkMenuItem.setTitleCondensed(mContext.getString(R.string.edit_bookmark));
} else {
bookmarkMenuItem.setIcon(R.drawable.btn_star);
bookmarkMenuItem.setChecked(false);
bookmarkMenuItem.setTitleCondensed(mContext.getString(R.string.menu_bookmark));
}
}
/**
* Updates the request desktop site item's state.
*
* @param menu {@link Menu} for request desktop site.
* @param currentTab Current tab being displayed.
*/
protected void updateRequestDesktopSiteMenuItem(
Menu menu, Tab currentTab, boolean canShowRequestDesktopSite) {
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.
String url = currentTab.getUrlString();
boolean isChromeScheme = url.startsWith(UrlConstants.CHROME_URL_PREFIX)
|| url.startsWith(UrlConstants.CHROME_NATIVE_URL_PREFIX);
boolean itemVisible = canShowRequestDesktopSite
&& (!isChromeScheme || currentTab.isNativePage())
&& !shouldShowReaderModePrefs(currentTab) && currentTab.getWebContents() != null;
requestMenuRow.setVisible(itemVisible);
if (!itemVisible) return;
boolean isRequestDesktopSite =
currentTab.getWebContents().getNavigationController().getUseDesktopUserAgent();
// 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));
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public boolean isIncognitoEnabled() {
return IncognitoUtils.isIncognitoModeEnabled();
}
@VisibleForTesting
static void setPageBookmarkedForTesting(Boolean bookmarked) {
sItemBookmarkedForTesting = bookmarked;
}
private boolean shouldShowRegroupedMenu() {
return CachedFeatureFlags.isEnabled(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP);
}
private boolean shouldShowThreeButtonActionBar() {
return CachedFeatureFlags.isEnabled(
ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR);
}
private boolean shouldShowShareInMenu() {
return getActionBarType() != ActionBarType.SHARE_BUTTON;
}
private boolean shouldShowInfoInMenu() {
return getActionBarType() != ActionBarType.STANDARD;
}
/**
* @return The type of action bar should be shown.
*/
private @ActionBarType int getActionBarType() {
if (shouldShowRegroupedMenu()) {
if (ACTION_BAR_VARIATION.getValue().equals("backward_button")) {
return ActionBarType.BACKWARD_BUTTON;
} else if (ACTION_BAR_VARIATION.getValue().equals("share_button")) {
return ActionBarType.SHARE_BUTTON;
}
}
return ActionBarType.STANDARD;
}
/**
* @return The type of three button action bar should be shown.
*/
private @ThreeButtonActionBarType int getThreeButtonActionBarType() {
if (shouldShowThreeButtonActionBar()) {
if (THREE_BUTTON_ACTION_BAR_VARIATION.getValue().equals("action_chip_view")) {
return ThreeButtonActionBarType.ACTION_CHIP_VIEW;
} else if (THREE_BUTTON_ACTION_BAR_VARIATION.getValue().equals(
"destination_chip_view")) {
return ThreeButtonActionBarType.DESTINATION_CHIP_VIEW;
}
}
return ThreeButtonActionBarType.DISABLED;
}
}