1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.app.appmenu; 6 7 import android.content.Context; 8 import android.content.pm.ResolveInfo; 9 import android.content.res.Resources; 10 import android.graphics.drawable.Drawable; 11 import android.os.Bundle; 12 import android.os.SystemClock; 13 import android.text.TextUtils; 14 import android.util.Pair; 15 import android.view.Menu; 16 import android.view.MenuItem; 17 import android.view.SubMenu; 18 import android.view.View; 19 20 import androidx.annotation.IntDef; 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import androidx.annotation.VisibleForTesting; 24 import androidx.appcompat.content.res.AppCompatResources; 25 import androidx.core.graphics.drawable.DrawableCompat; 26 27 import org.chromium.base.Callback; 28 import org.chromium.base.CallbackController; 29 import org.chromium.base.CommandLine; 30 import org.chromium.base.ContextUtils; 31 import org.chromium.base.metrics.RecordHistogram; 32 import org.chromium.base.supplier.ObservableSupplier; 33 import org.chromium.base.supplier.OneshotSupplier; 34 import org.chromium.chrome.R; 35 import org.chromium.chrome.browser.ActivityTabProvider; 36 import org.chromium.chrome.browser.ShortcutHelper; 37 import org.chromium.chrome.browser.banners.AppBannerManager; 38 import org.chromium.chrome.browser.banners.AppMenuVerbiage; 39 import org.chromium.chrome.browser.bookmarks.BookmarkBridge; 40 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; 41 import org.chromium.chrome.browser.device.DeviceClassManager; 42 import org.chromium.chrome.browser.download.DownloadUtils; 43 import org.chromium.chrome.browser.flags.CachedFeatureFlags; 44 import org.chromium.chrome.browser.flags.ChromeFeatureList; 45 import org.chromium.chrome.browser.flags.ChromeSwitches; 46 import org.chromium.chrome.browser.flags.StringCachedFieldTrialParameter; 47 import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController; 48 import org.chromium.chrome.browser.incognito.IncognitoUtils; 49 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher; 50 import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper; 51 import org.chromium.chrome.browser.share.ShareHelper; 52 import org.chromium.chrome.browser.share.ShareUtils; 53 import org.chromium.chrome.browser.tab.Tab; 54 import org.chromium.chrome.browser.tabmodel.TabModelSelector; 55 import org.chromium.chrome.browser.tasks.tab_management.PriceTrackingUtilities; 56 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities; 57 import org.chromium.chrome.browser.toolbar.ToolbarManager; 58 import org.chromium.chrome.browser.translate.TranslateUtils; 59 import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler; 60 import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate; 61 import org.chromium.chrome.browser.ui.appmenu.CustomViewBinder; 62 import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration; 63 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; 64 import org.chromium.components.embedder_support.util.UrlConstants; 65 import org.chromium.components.webapk.lib.client.WebApkValidator; 66 import org.chromium.ui.base.DeviceFormFactor; 67 import org.chromium.ui.modaldialog.ModalDialogManager; 68 69 import java.util.ArrayList; 70 import java.util.Arrays; 71 import java.util.HashSet; 72 import java.util.LinkedHashMap; 73 import java.util.List; 74 import java.util.Map; 75 import java.util.Set; 76 77 /** 78 * Base implementation of {@link AppMenuPropertiesDelegate} that handles hiding and showing menu 79 * items based on activity state. 80 */ 81 public class AppMenuPropertiesDelegateImpl implements AppMenuPropertiesDelegate { 82 public static final StringCachedFieldTrialParameter ACTION_BAR_VARIATION = 83 new StringCachedFieldTrialParameter( 84 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP, "action_bar", ""); 85 public static final StringCachedFieldTrialParameter THREE_BUTTON_ACTION_BAR_VARIATION = 86 new StringCachedFieldTrialParameter( 87 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR, 88 "three_button_action_bar", ""); 89 90 private static Boolean sItemBookmarkedForTesting; 91 92 protected MenuItem mReloadMenuItem; 93 94 protected final Context mContext; 95 protected final boolean mIsTablet; 96 protected final ActivityTabProvider mActivityTabProvider; 97 protected final MultiWindowModeStateDispatcher mMultiWindowModeStateDispatcher; 98 protected final TabModelSelector mTabModelSelector; 99 protected final ToolbarManager mToolbarManager; 100 protected final View mDecorView; 101 private CallbackController mCallbackController = new CallbackController(); 102 private final ObservableSupplier<BookmarkBridge> mBookmarkBridgeSupplier; 103 private Callback<BookmarkBridge> mBookmarkBridgeSupplierCallback; 104 private boolean mUpdateMenuItemVisible; 105 private ShareUtils mShareUtils; 106 // Keeps track of which menu item was shown when installable app is detected. 107 private int mAddAppTitleShown; 108 private final ModalDialogManager mModalDialogManager; 109 110 // The keys of the Map are menuitem ids, the first elements in the Pair are menuitem ids, 111 // and the second elements in the Pair are AppMenuSimilarSelectionType. If users first 112 // selected the menuitems in the Pair.first, and then selected a menuitem which is the key 113 // if the Map, then users' selection match the pattern Pair.second. 114 private static final Map<Integer, Pair<Set<Integer>, Integer>> sSimilarSelectedMenuItemMap = 115 createSimilarSelectedMap(); 116 117 @VisibleForTesting 118 @IntDef({MenuGroup.INVALID, MenuGroup.PAGE_MENU, MenuGroup.OVERVIEW_MODE_MENU, 119 MenuGroup.START_SURFACE_MODE_MENU, MenuGroup.TABLET_EMPTY_MODE_MENU}) 120 @interface MenuGroup { 121 int INVALID = -1; 122 int PAGE_MENU = 0; 123 int OVERVIEW_MODE_MENU = 1; 124 int START_SURFACE_MODE_MENU = 2; 125 int TABLET_EMPTY_MODE_MENU = 3; 126 } 127 128 @IntDef({ActionBarType.STANDARD, ActionBarType.BACKWARD_BUTTON, ActionBarType.SHARE_BUTTON}) 129 @interface ActionBarType { 130 int STANDARD = 0; 131 int BACKWARD_BUTTON = 1; 132 int SHARE_BUTTON = 2; 133 } 134 135 @IntDef({ThreeButtonActionBarType.DISABLED, ThreeButtonActionBarType.ACTION_CHIP_VIEW, 136 ThreeButtonActionBarType.DESTINATION_CHIP_VIEW, ThreeButtonActionBarType.ADD_TO_OPTION}) 137 @interface ThreeButtonActionBarType { 138 int DISABLED = 0; 139 int ACTION_CHIP_VIEW = 1; 140 int DESTINATION_CHIP_VIEW = 2; 141 int ADD_TO_OPTION = 3; 142 } 143 144 /** 145 * Keep this list sync with AppMenuSimilarSelectionType in enums.xml. 146 */ 147 @IntDef({AppMenuSimilarSelectionType.NO_MATCH, 148 AppMenuSimilarSelectionType.BOOKMARK_PAGE_THEN_ALL_BOOKMARKS, 149 AppMenuSimilarSelectionType.ALL_BOOKMARKS_THEN_BOOKMARK_PAGE, 150 AppMenuSimilarSelectionType.DOWNLOAD_PAGE_THEN_ALL_DOWNLOADS, 151 AppMenuSimilarSelectionType.ALL_DOWNLOADS_THEN_DOWNLOAD_PAGE}) 152 @interface AppMenuSimilarSelectionType { 153 int NO_MATCH = -1; 154 int BOOKMARK_PAGE_THEN_ALL_BOOKMARKS = 0; 155 int ALL_BOOKMARKS_THEN_BOOKMARK_PAGE = 1; 156 int DOWNLOAD_PAGE_THEN_ALL_DOWNLOADS = 2; 157 int ALL_DOWNLOADS_THEN_DOWNLOAD_PAGE = 3; 158 int NUM_ENTRIES = 4; 159 } 160 161 protected @Nullable OverviewModeBehavior mOverviewModeBehavior; 162 protected BookmarkBridge mBookmarkBridge; 163 protected Runnable mAppMenuInvalidator; 164 165 /** 166 * Construct a new {@link AppMenuPropertiesDelegateImpl}. 167 * @param context The activity context. 168 * @param activityTabProvider The {@link ActivityTabProvider} for the containing activity. 169 * @param multiWindowModeStateDispatcher The {@link MultiWindowModeStateDispatcher} for the 170 * containing activity. 171 * @param tabModelSelector The {@link TabModelSelector} for the containing activity. 172 * @param toolbarManager The {@link ToolbarManager} for the containing activity. 173 * @param decorView The decor {@link View}, e.g. from Window#getDecorView(), for the containing 174 * activity. 175 * @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the 176 * {@link OverviewModeBehavior} associated with the containing activity. 177 * @param bookmarkBridgeSupplier An {@link ObservableSupplier} for the {@link BookmarkBridge} 178 * associated with the containing activity. 179 * @param modalDialogManager The {@link ModalDialogManager} that should be used to show "Add To" 180 * dialog. 181 */ AppMenuPropertiesDelegateImpl(Context context, ActivityTabProvider activityTabProvider, MultiWindowModeStateDispatcher multiWindowModeStateDispatcher, TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView, @Nullable OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier, ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier, ModalDialogManager modalDialogManager)182 public AppMenuPropertiesDelegateImpl(Context context, ActivityTabProvider activityTabProvider, 183 MultiWindowModeStateDispatcher multiWindowModeStateDispatcher, 184 TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView, 185 @Nullable OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier, 186 ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier, 187 ModalDialogManager modalDialogManager) { 188 mContext = context; 189 mIsTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext); 190 mActivityTabProvider = activityTabProvider; 191 mMultiWindowModeStateDispatcher = multiWindowModeStateDispatcher; 192 mTabModelSelector = tabModelSelector; 193 mToolbarManager = toolbarManager; 194 mDecorView = decorView; 195 mModalDialogManager = modalDialogManager; 196 197 if (overviewModeBehaviorSupplier != null) { 198 overviewModeBehaviorSupplier.onAvailable(mCallbackController.makeCancelable( 199 overviewModeBehavior -> { mOverviewModeBehavior = overviewModeBehavior; })); 200 } 201 202 mBookmarkBridgeSupplier = bookmarkBridgeSupplier; 203 mBookmarkBridgeSupplierCallback = (bookmarkBridge) -> mBookmarkBridge = bookmarkBridge; 204 mBookmarkBridgeSupplier.addObserver(mBookmarkBridgeSupplierCallback); 205 mShareUtils = new ShareUtils(); 206 } 207 208 @Override destroy()209 public void destroy() { 210 mBookmarkBridgeSupplier.removeObserver(mBookmarkBridgeSupplierCallback); 211 if (mCallbackController != null) { 212 mCallbackController.destroy(); 213 mCallbackController = null; 214 } 215 } 216 217 @Override getAppMenuLayoutId()218 public int getAppMenuLayoutId() { 219 if (shouldShowRegroupedMenu() || shouldShowThreeButtonActionBar()) { 220 return R.menu.main_menu_regroup; 221 } 222 return R.menu.main_menu; 223 } 224 225 @Override getCustomViewBinders()226 public @Nullable List<CustomViewBinder> getCustomViewBinders() { 227 List<CustomViewBinder> customViewBinders = new ArrayList<>(); 228 customViewBinders.add(new UpdateMenuItemViewBinder()); 229 customViewBinders.add(new ManagedByMenuItemViewBinder()); 230 customViewBinders.add(new IncognitoMenuItemViewBinder()); 231 customViewBinders.add(new DividerLineMenuItemViewBinder()); 232 customViewBinders.add(new ChipViewMenuItemViewBinder(getThreeButtonActionBarType())); 233 customViewBinders.add(new AddToMenuItemViewBinder(mContext, mModalDialogManager)); 234 return customViewBinders; 235 } 236 237 /** 238 * @return Whether the app menu for a web page should be shown. 239 */ shouldShowPageMenu()240 protected boolean shouldShowPageMenu() { 241 boolean isOverview = 242 mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible(); 243 244 if (mIsTablet) { 245 boolean hasTabs = mTabModelSelector.getCurrentModel().getCount() != 0; 246 return hasTabs && !isOverview; 247 } else { 248 return !isOverview && mActivityTabProvider.get() != null; 249 } 250 } 251 252 @VisibleForTesting 253 @MenuGroup getMenuGroup()254 int getMenuGroup() { 255 // Determine which menu to show. 256 @MenuGroup 257 int menuGroup = MenuGroup.INVALID; 258 if (shouldShowPageMenu()) menuGroup = MenuGroup.PAGE_MENU; 259 260 boolean isOverview = 261 mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible(); 262 if (mIsTablet) { 263 boolean hasTabs = mTabModelSelector.getCurrentModel().getCount() != 0; 264 if (hasTabs && isOverview) { 265 menuGroup = MenuGroup.OVERVIEW_MODE_MENU; 266 } else if (!hasTabs) { 267 menuGroup = MenuGroup.TABLET_EMPTY_MODE_MENU; 268 } 269 } else if (isOverview) { 270 menuGroup = StartSurfaceConfiguration.isStartSurfaceEnabled() 271 ? MenuGroup.START_SURFACE_MODE_MENU 272 : MenuGroup.OVERVIEW_MODE_MENU; 273 } 274 assert menuGroup != MenuGroup.INVALID; 275 return menuGroup; 276 } 277 setMenuGroupVisibility(@enuGroup int menuGroup, Menu menu)278 private void setMenuGroupVisibility(@MenuGroup int menuGroup, Menu menu) { 279 menu.setGroupVisible(R.id.PAGE_MENU, menuGroup == MenuGroup.PAGE_MENU); 280 menu.setGroupVisible(R.id.OVERVIEW_MODE_MENU, menuGroup == MenuGroup.OVERVIEW_MODE_MENU); 281 menu.setGroupVisible( 282 R.id.START_SURFACE_MODE_MENU, menuGroup == MenuGroup.START_SURFACE_MODE_MENU); 283 menu.setGroupVisible( 284 R.id.TABLET_EMPTY_MODE_MENU, menuGroup == MenuGroup.TABLET_EMPTY_MODE_MENU); 285 } 286 287 @Override prepareMenu(Menu menu, AppMenuHandler handler)288 public void prepareMenu(Menu menu, AppMenuHandler handler) { 289 int menuGroup = getMenuGroup(); 290 setMenuGroupVisibility(menuGroup, menu); 291 292 boolean isIncognito = mTabModelSelector.getCurrentModel().isIncognito(); 293 Tab currentTab = mActivityTabProvider.get(); 294 295 if (menuGroup == MenuGroup.PAGE_MENU && currentTab != null) { 296 preparePageMenu(menu, currentTab, handler, isIncognito); 297 } 298 prepareCommonMenuItems(menu, menuGroup, isIncognito); 299 } 300 preparePageMenu( Menu menu, Tab currentTab, AppMenuHandler handler, boolean isIncognito)301 private void preparePageMenu( 302 Menu menu, Tab currentTab, AppMenuHandler handler, boolean isIncognito) { 303 String url = currentTab.getUrlString(); 304 boolean isChromeScheme = url.startsWith(UrlConstants.CHROME_URL_PREFIX) 305 || url.startsWith(UrlConstants.CHROME_NATIVE_URL_PREFIX); 306 boolean isFileScheme = url.startsWith(UrlConstants.FILE_URL_PREFIX); 307 boolean isContentScheme = url.startsWith(UrlConstants.CONTENT_URL_PREFIX); 308 309 // Update the icon row items (shown in narrow form factors). 310 boolean shouldShowIconRow = shouldShowIconRow(); 311 menu.findItem(R.id.icon_row_menu_id).setVisible(shouldShowIconRow); 312 if (shouldShowIconRow) { 313 SubMenu actionBar = menu.findItem(R.id.icon_row_menu_id).getSubMenu(); 314 315 @ActionBarType 316 int actionBarType = getActionBarType(); 317 MenuItem backwardMenuItem = actionBar.findItem(R.id.backward_menu_id); 318 if (backwardMenuItem != null) { 319 if (actionBarType == ActionBarType.BACKWARD_BUTTON) { 320 backwardMenuItem.setEnabled(currentTab.canGoBack()); 321 } else { 322 actionBar.removeItem(R.id.backward_menu_id); 323 } 324 } 325 326 // Disable the "Forward" menu item if there is no page to go to. 327 MenuItem forwardMenuItem = actionBar.findItem(R.id.forward_menu_id); 328 forwardMenuItem.setEnabled(currentTab.canGoForward()); 329 330 mReloadMenuItem = actionBar.findItem(R.id.reload_menu_id); 331 Drawable icon = AppCompatResources.getDrawable(mContext, R.drawable.btn_reload_stop); 332 DrawableCompat.setTintList(icon, 333 AppCompatResources.getColorStateList( 334 mContext, R.color.default_icon_color_tint_list)); 335 mReloadMenuItem.setIcon(icon); 336 loadingStateChanged(currentTab.isLoading()); 337 338 MenuItem bookmarkMenuItem = actionBar.findItem(R.id.bookmark_this_page_id); 339 if (shouldShowThreeButtonActionBar()) { 340 actionBar.removeItem(R.id.bookmark_this_page_id); 341 } else { 342 updateBookmarkMenuItem(bookmarkMenuItem, currentTab); 343 } 344 345 MenuItem offlineMenuItem = actionBar.findItem(R.id.offline_page_id); 346 if (offlineMenuItem != null) { 347 if (shouldShowThreeButtonActionBar()) { 348 actionBar.removeItem(R.id.offline_page_id); 349 } else { 350 offlineMenuItem.setEnabled(shouldEnableDownloadPage(currentTab)); 351 } 352 } 353 354 MenuItem shareMenuItem = actionBar.findItem(R.id.share_menu_button_id); 355 if (shareMenuItem != null) { 356 if (shouldShowShareInMenu()) { 357 actionBar.removeItem(R.id.share_menu_button_id); 358 } else { 359 shareMenuItem.setEnabled(mShareUtils.shouldEnableShare(currentTab)); 360 } 361 } 362 363 if (shouldShowInfoInMenu()) { 364 actionBar.removeItem(R.id.info_menu_id); 365 } 366 367 if (shouldShowThreeButtonActionBar()) { 368 assert actionBar.size() == 3; 369 } else { 370 assert actionBar.size() == 5; 371 } 372 } 373 374 mUpdateMenuItemVisible = shouldShowUpdateMenuItem(); 375 menu.findItem(R.id.update_menu_id).setVisible(mUpdateMenuItemVisible); 376 if (mUpdateMenuItemVisible) { 377 mAppMenuInvalidator = () -> handler.invalidateAppMenu(); 378 UpdateMenuItemHelper.getInstance().registerObserver(mAppMenuInvalidator); 379 } 380 381 menu.findItem(R.id.move_to_other_window_menu_id).setVisible(shouldShowMoveToOtherWindow()); 382 383 @ThreeButtonActionBarType 384 int threeButtonActionBarType = getThreeButtonActionBarType(); 385 boolean addToOptionVisible = 386 threeButtonActionBarType == ThreeButtonActionBarType.ADD_TO_OPTION; 387 MenuItem addToDividerLineItem = menu.findItem(R.id.add_to_divider_line_id); 388 if (addToDividerLineItem != null) { 389 addToDividerLineItem.setVisible(addToOptionVisible); 390 addToDividerLineItem.setEnabled(false); 391 } 392 // Duplicating add_to_homescreen/install_app/open_webapk is for 393 // the purpose of experiment, one of them will be removed once the 394 // experiments are done. 395 MenuItem addToMenuItem = menu.findItem(R.id.add_to_menu_id); 396 if (addToMenuItem != null) { 397 addToMenuItem.setVisible(addToOptionVisible); 398 } 399 MenuItem installAppItem = menu.findItem(R.id.install_app_id); 400 if (installAppItem != null) { 401 // Visible will be changed later by #prepareAddToHomescreenMenuItem. 402 installAppItem.setVisible(addToOptionVisible); 403 } 404 MenuItem menuOpenWebApkItem = menu.findItem(R.id.menu_open_webapk_id); 405 if (menuOpenWebApkItem != null) { 406 // Visible will be changed later by #prepareAddToHomescreenMenuItem. 407 menuOpenWebApkItem.setVisible(addToOptionVisible); 408 } 409 410 if (shouldShowThreeButtonActionBar()) { 411 MenuItem downloadMenuItem = 412 menu.findItem(R.id.downloads_row_menu_id).getSubMenu().getItem(1); 413 assert downloadMenuItem.getItemId() == R.id.offline_page_chip_id; 414 downloadMenuItem.setEnabled(shouldEnableDownloadPage(currentTab)); 415 416 MenuItem bookmarkMenuItem = 417 menu.findItem(R.id.all_bookmarks_row_menu_id).getSubMenu().getItem(1); 418 assert bookmarkMenuItem.getItemId() == R.id.bookmark_this_page_chip_id; 419 updateBookmarkMenuItem(bookmarkMenuItem, currentTab); 420 421 // Update titles for ChipView menu items. 422 if (threeButtonActionBarType == ThreeButtonActionBarType.ACTION_CHIP_VIEW) { 423 downloadMenuItem.setTitle(R.string.add); 424 if (bookmarkMenuItem.isChecked()) { 425 bookmarkMenuItem.setTitle(R.string.bookmark_item_edit); 426 } else { 427 bookmarkMenuItem.setTitle(R.string.add); 428 } 429 } else if (threeButtonActionBarType == ThreeButtonActionBarType.DESTINATION_CHIP_VIEW) { 430 MenuItem allDownloadMenuItem = 431 menu.findItem(R.id.downloads_row_menu_id).getSubMenu().getItem(0); 432 assert allDownloadMenuItem.getItemId() == R.id.downloads_menu_id; 433 allDownloadMenuItem.setTitle(R.string.all); 434 435 MenuItem allBookmarkMenuItem = 436 menu.findItem(R.id.all_bookmarks_row_menu_id).getSubMenu().getItem(0); 437 assert allBookmarkMenuItem.getItemId() == R.id.all_bookmarks_menu_id; 438 allBookmarkMenuItem.setTitle(R.string.all); 439 } else if (threeButtonActionBarType == ThreeButtonActionBarType.ADD_TO_OPTION) { 440 MenuItem addToBookmarksMenuItem = 441 addToMenuItem.getSubMenu().findItem(R.id.add_to_bookmarks_menu_id); 442 updateBookmarkMenuItem(addToBookmarksMenuItem, currentTab); 443 444 MenuItem addToReadingListMenuItem = 445 addToMenuItem.getSubMenu().findItem(R.id.add_to_reading_list_menu_id); 446 addToReadingListMenuItem.setVisible( 447 CachedFeatureFlags.isEnabled(ChromeFeatureList.READ_LATER)); 448 449 MenuItem addToDownloadsMenuItem = 450 addToMenuItem.getSubMenu().findItem(R.id.add_to_downloads_menu_id); 451 addToDownloadsMenuItem.setEnabled(shouldEnableDownloadPage(currentTab)); 452 453 MenuItem addToHomescreenMenuItem = 454 addToMenuItem.getSubMenu().findItem(R.id.add_to_homescreen_menu_id); 455 prepareAddToHomescreenMenuItem(addToHomescreenMenuItem, installAppItem, 456 menuOpenWebApkItem, menu, currentTab, 457 shouldShowHomeScreenMenuItem( 458 isChromeScheme, isFileScheme, isContentScheme, isIncognito, url)); 459 if (addToHomescreenMenuItem.isVisible()) { 460 // addToHomescreenMenuItem in "Add to" dialog uses a different string. 461 addToHomescreenMenuItem.setTitle(R.string.menu_homescreen); 462 } 463 } 464 } 465 466 // Don't allow either "chrome://" pages or interstitial pages to be shared. 467 menu.findItem(R.id.share_row_menu_id) 468 .setVisible(mShareUtils.shouldEnableShare(currentTab) && shouldShowShareInMenu()); 469 470 ShareHelper.configureDirectShareMenuItem( 471 mContext, menu.findItem(R.id.direct_share_menu_id)); 472 473 menu.findItem(R.id.paint_preview_show_id) 474 .setVisible(shouldShowPaintPreview(isChromeScheme, currentTab, isIncognito)); 475 476 // Enable image descriptions if the feature flag is enabled, and if a screen reader 477 // is currently running. 478 if (ImageDescriptionsController.getInstance().shouldShowImageDescriptionsMenuItem()) { 479 menu.findItem(R.id.get_image_descriptions_id).setVisible(true); 480 menu.findItem(R.id.get_image_descriptions_id) 481 .setTitle(ImageDescriptionsController.getInstance().imageDescriptionsEnabled() 482 ? R.string.menu_stop_image_descriptions 483 : R.string.menu_get_image_descriptions); 484 } else { 485 menu.findItem(R.id.get_image_descriptions_id).setVisible(false); 486 } 487 488 // Disable find in page on the native NTP. 489 menu.findItem(R.id.find_in_page_id).setVisible(shouldShowFindInPage(currentTab)); 490 491 // Prepare translate menu button. 492 prepareTranslateMenuItem(menu, currentTab); 493 494 MenuItem homescreenItem = menu.findItem(R.id.add_to_homescreen_id); 495 MenuItem openWebApkItem = menu.findItem(R.id.open_webapk_id); 496 if (addToOptionVisible) { 497 homescreenItem.setVisible(false); 498 openWebApkItem.setVisible(false); 499 } else { 500 prepareAddToHomescreenMenuItem(homescreenItem, null, openWebApkItem, menu, currentTab, 501 shouldShowHomeScreenMenuItem( 502 isChromeScheme, isFileScheme, isContentScheme, isIncognito, url)); 503 } 504 505 updateRequestDesktopSiteMenuItem(menu, currentTab, true /* can show */); 506 507 // Only display reader mode settings menu option if the current page is in reader mode. 508 menu.findItem(R.id.reader_mode_prefs_id).setVisible(shouldShowReaderModePrefs(currentTab)); 509 510 MenuItem infoMenuItem = menu.findItem(R.id.info_id); 511 if (infoMenuItem != null) { 512 infoMenuItem.setVisible(shouldShowInfoInMenu()); 513 } 514 515 // Only display the Enter VR button if VR Shell Dev environment is enabled. 516 menu.findItem(R.id.enter_vr_id).setVisible(shouldShowEnterVr()); 517 518 MenuItem managedByMenuItem = menu.findItem(R.id.managed_by_menu_id); 519 managedByMenuItem.setVisible(shouldShowManagedByMenuItem(currentTab)); 520 // TODO(https://crbug.com/1092175): Enable "managed by" menu item after chrome://management 521 // page is added. 522 managedByMenuItem.setEnabled(false); 523 } 524 prepareCommonMenuItems(Menu menu, @MenuGroup int menuGroup, boolean isIncognito)525 private void prepareCommonMenuItems(Menu menu, @MenuGroup int menuGroup, boolean isIncognito) { 526 // We have to iterate all menu items since same menu item ID may be associated with more 527 // than one menu items. 528 boolean isMenuGroupTabsVisible = TabUiFeatureUtilities.isTabGroupsAndroidEnabled() 529 && !DeviceClassManager.enableAccessibilityLayout(); 530 boolean isMenuGroupTabsEnabled = isMenuGroupTabsVisible 531 && mTabModelSelector.getTabModelFilterProvider() 532 .getCurrentTabModelFilter() 533 .getTabsWithNoOtherRelatedTabs() 534 .size() 535 > 1; 536 boolean isPriceTrackingVisible = TabUiFeatureUtilities.isPriceTrackingEnabled() 537 && !DeviceClassManager.enableAccessibilityLayout(); 538 boolean isPriceTrackingEnabled = isPriceTrackingVisible; 539 540 for (int i = 0; i < menu.size(); ++i) { 541 MenuItem item = menu.getItem(i); 542 if (!shouldShowIconBeforeItem()) { 543 // Remove icons for menu items except the reader mode prefs and the update menu 544 // item. 545 if (item.getItemId() != R.id.reader_mode_prefs_id 546 && item.getItemId() != R.id.update_menu_id) { 547 item.setIcon(null); 548 } 549 // Remove icons for menu items that have submenus. 550 if (item.getItemId() == R.id.downloads_row_menu_id 551 || item.getItemId() == R.id.all_bookmarks_row_menu_id 552 || item.getItemId() == R.id.add_to_menu_id) { 553 for (int j = 0; j < item.getSubMenu().size(); ++j) { 554 item.getSubMenu().getItem(j).setIcon(null); 555 } 556 } 557 } 558 559 if (item.getItemId() == R.id.new_incognito_tab_menu_id && item.isVisible()) { 560 // Disable new incognito tab when it is blocked (e.g. by a policy). 561 // findItem(...).setEnabled(...)" is not enough here, because of the inflated 562 // main_menu.xml contains multiple items with the same id in different groups 563 // e.g.: menu_new_incognito_tab. 564 item.setEnabled(isIncognitoEnabled()); 565 } 566 567 if (item.getItemId() == R.id.divider_line_id) { 568 item.setEnabled(false); 569 } 570 571 int itemGroupId = item.getGroupId(); 572 if (!(menuGroup == MenuGroup.START_SURFACE_MODE_MENU 573 && itemGroupId == R.id.START_SURFACE_MODE_MENU 574 || menuGroup == MenuGroup.OVERVIEW_MODE_MENU 575 && itemGroupId == R.id.OVERVIEW_MODE_MENU 576 || menuGroup == MenuGroup.PAGE_MENU && itemGroupId == R.id.PAGE_MENU)) { 577 continue; 578 } 579 580 if (item.getItemId() == R.id.recent_tabs_menu_id) { 581 item.setVisible(!isIncognito); 582 } 583 if (item.getItemId() == R.id.menu_group_tabs) { 584 item.setVisible(isMenuGroupTabsVisible); 585 item.setEnabled(isMenuGroupTabsEnabled); 586 } 587 if (item.getItemId() == R.id.track_prices_row_menu_id) { 588 item.setVisible(isPriceTrackingVisible); 589 item.setEnabled(isPriceTrackingEnabled); 590 if (isPriceTrackingVisible) { 591 menu.findItem(R.id.track_prices_check_id) 592 .setChecked(PriceTrackingUtilities.isTrackPricesOnTabsEnabled()); 593 } 594 } 595 if (item.getItemId() == R.id.close_all_tabs_menu_id) { 596 boolean hasTabs = mTabModelSelector.getTotalTabCount() > 0; 597 item.setVisible(!isIncognito); 598 item.setEnabled(hasTabs); 599 } 600 if (item.getItemId() == R.id.close_all_incognito_tabs_menu_id) { 601 boolean hasIncognitoTabs = mTabModelSelector.getModel(true).getCount() > 0; 602 item.setVisible(isIncognito); 603 item.setEnabled(hasIncognitoTabs); 604 } 605 } 606 } 607 608 /** 609 * @param currentTab The currentTab for which the app menu is showing. 610 * @return Whether the reader mode preferences menu item should be displayed. 611 */ 612 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) shouldShowReaderModePrefs(@onNull Tab currentTab)613 public boolean shouldShowReaderModePrefs(@NonNull Tab currentTab) { 614 return DomDistillerUrlUtils.isDistilledPage(currentTab.getUrlString()); 615 } 616 617 /** 618 * @param currentTab The currentTab for which the app menu is showing. 619 * @return Whether the {@code currentTab} may be downloaded, indicating whether the download 620 * page menu item should be enabled. 621 */ 622 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) shouldEnableDownloadPage(@onNull Tab currentTab)623 public boolean shouldEnableDownloadPage(@NonNull Tab currentTab) { 624 return DownloadUtils.isAllowedToDownloadPage(currentTab); 625 } 626 627 /** 628 * @param currentTab The currentTab for which the app menu is showing. 629 * @return Whether bookmark page menu item should be checked, indicating that the current tab 630 * is bookmarked. 631 */ 632 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) shouldCheckBookmarkStar(@onNull Tab currentTab)633 public boolean shouldCheckBookmarkStar(@NonNull Tab currentTab) { 634 return sItemBookmarkedForTesting != null 635 ? sItemBookmarkedForTesting 636 : mBookmarkBridge != null && mBookmarkBridge.hasBookmarkIdForTab(currentTab); 637 } 638 639 /** 640 * @return Whether the update Chrome menu item should be displayed. 641 */ shouldShowUpdateMenuItem()642 protected boolean shouldShowUpdateMenuItem() { 643 return UpdateMenuItemHelper.getInstance().getUiState().itemState != null; 644 } 645 646 /** 647 * @return Whether the "Move to other window" menu item should be displayed. 648 */ shouldShowMoveToOtherWindow()649 protected boolean shouldShowMoveToOtherWindow() { 650 boolean hasMoreThanOneTab = mTabModelSelector.getTotalTabCount() > 1; 651 return mMultiWindowModeStateDispatcher.isOpenInOtherWindowSupported() && hasMoreThanOneTab; 652 } 653 654 /** 655 * @param isChromeScheme Whether URL for the current tab starts with the chrome:// scheme. 656 * @param currentTab The currentTab for which the app menu is showing. 657 * @param isIncognito Whether the currentTab is incognito. 658 * @return Whether the paint preview menu item should be displayed. 659 */ 660 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) shouldShowPaintPreview( boolean isChromeScheme, @NonNull Tab currentTab, boolean isIncognito)661 public boolean shouldShowPaintPreview( 662 boolean isChromeScheme, @NonNull Tab currentTab, boolean isIncognito) { 663 return CachedFeatureFlags.isEnabled(ChromeFeatureList.PAINT_PREVIEW_DEMO) && !isChromeScheme 664 && !isIncognito; 665 } 666 667 /** 668 * @param currentTab The currentTab for which the app menu is showing. 669 * @return Whether the find in page menu item should be displayed. 670 */ shouldShowFindInPage(@onNull Tab currentTab)671 protected boolean shouldShowFindInPage(@NonNull Tab currentTab) { 672 return !currentTab.isNativePage() && currentTab.getWebContents() != null; 673 } 674 675 /** 676 * @return Whether the enter VR menu item should be displayed. 677 */ shouldShowEnterVr()678 protected boolean shouldShowEnterVr() { 679 return CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_VR_SHELL_DEV); 680 } 681 682 /** 683 * This method should only be called once per context menu shown. 684 * @param currentTab The currentTab for which the app menu is showing. 685 * @param logging Whether logging should be performed in this check. 686 * @return Whether the translate menu item should be displayed. 687 */ 688 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) shouldShowTranslateMenuItem(@onNull Tab currentTab)689 public boolean shouldShowTranslateMenuItem(@NonNull Tab currentTab) { 690 return TranslateUtils.canTranslateCurrentTab(currentTab, true); 691 } 692 693 /** 694 * @param isChromeScheme Whether URL for the current tab starts with the chrome:// scheme. 695 * @param isFileScheme Whether URL for the current tab starts with the file:// scheme. 696 * @param isContentScheme Whether URL for the current tab starts with the file:// scheme. 697 * @param isIncognito Whether the current tab is incognito. 698 * @param url The URL for the current tab. 699 * @return Whether the homescreen menu item should be displayed. 700 */ shouldShowHomeScreenMenuItem(boolean isChromeScheme, boolean isFileScheme, boolean isContentScheme, boolean isIncognito, String url)701 protected boolean shouldShowHomeScreenMenuItem(boolean isChromeScheme, boolean isFileScheme, 702 boolean isContentScheme, boolean isIncognito, String url) { 703 // Hide 'Add to homescreen' for the following: 704 // * chrome:// pages - Android doesn't know how to direct those URLs. 705 // * incognito pages - To avoid problems where users create shortcuts in incognito 706 // mode and then open the webapp in regular mode. 707 // * file:// - After API 24, file: URIs are not supported in VIEW intents and thus 708 // can not be added to the homescreen. 709 // * content:// - Accessing external content URIs requires the calling app to grant 710 // access to the resource via FLAG_GRANT_READ_URI_PERMISSION, and that 711 // is not persisted when adding to the homescreen. 712 // * If creating shortcuts it not supported by the current home screen. 713 return ShortcutHelper.isAddToHomeIntentSupported() && !isChromeScheme && !isFileScheme 714 && !isContentScheme && !isIncognito && !TextUtils.isEmpty(url); 715 } 716 717 /** 718 * @param currentTab Current tab being displayed. 719 * @return Whether the "Managed by your organization" menu item should be displayed. 720 */ shouldShowManagedByMenuItem(Tab currentTab)721 protected boolean shouldShowManagedByMenuItem(Tab currentTab) { 722 return false; 723 } 724 725 /** 726 * Sets the visibility and labels of the "Add to Home screen" and "Open WebAPK" menu items. 727 */ prepareAddToHomescreenMenuItem(MenuItem homescreenItem, @Nullable MenuItem installAppItem, MenuItem openWebApkItem, Menu menu, Tab currentTab, boolean shouldShowHomeScreenMenuItem)728 protected void prepareAddToHomescreenMenuItem(MenuItem homescreenItem, 729 @Nullable MenuItem installAppItem, MenuItem openWebApkItem, Menu menu, Tab currentTab, 730 boolean shouldShowHomeScreenMenuItem) { 731 mAddAppTitleShown = AppMenuVerbiage.APP_MENU_OPTION_UNKNOWN; 732 if (!shouldShowHomeScreenMenuItem) { 733 homescreenItem.setVisible(false); 734 openWebApkItem.setVisible(false); 735 if (installAppItem != null) { 736 installAppItem.setVisible(false); 737 } 738 return; 739 } 740 741 Context context = ContextUtils.getApplicationContext(); 742 long addToHomeScreenStart = SystemClock.elapsedRealtime(); 743 ResolveInfo resolveInfo = 744 WebApkValidator.queryFirstWebApkResolveInfo(context, currentTab.getUrlString()); 745 RecordHistogram.recordTimesHistogram("Android.PrepareMenu.OpenWebApkVisibilityCheck", 746 SystemClock.elapsedRealtime() - addToHomeScreenStart); 747 748 boolean openWebApkItemVisible = 749 resolveInfo != null && resolveInfo.activityInfo.packageName != null; 750 751 if (openWebApkItemVisible) { 752 String appName = resolveInfo.loadLabel(context.getPackageManager()).toString(); 753 openWebApkItem.setTitle(context.getString(R.string.menu_open_webapk, appName)); 754 755 homescreenItem.setVisible(false); 756 openWebApkItem.setVisible(true); 757 if (installAppItem != null) { 758 installAppItem.setVisible(false); 759 } 760 } else { 761 AppBannerManager.InstallStringPair installStrings = getAddToHomeScreenTitle(currentTab); 762 // When "Add to" mernu item is enabled for the app menu, if the current webpage is a PWA 763 // then the menu item to "Install app" ({@code installAppItem}) will be shown in the 764 // main menu. If the current webpage is not a PWA "Add to homescreen" will be shown in 765 // the "Add to dialog" instead. If {@code installAppItem} is not null, ensure that only 766 // one of installAppItem or homescreenItem are visible. 767 if (installAppItem != null 768 && installStrings.titleTextId == AppBannerManager.PWA_PAIR.titleTextId) { 769 installAppItem.setTitle(installStrings.titleTextId); 770 installAppItem.setVisible(true); 771 homescreenItem.setVisible(false); 772 } else { 773 homescreenItem.setTitle(installStrings.titleTextId); 774 homescreenItem.setVisible(true); 775 if (installAppItem != null) { 776 installAppItem.setVisible(false); 777 } 778 } 779 openWebApkItem.setVisible(false); 780 781 if (installStrings.titleTextId == AppBannerManager.NON_PWA_PAIR.titleTextId) { 782 mAddAppTitleShown = AppMenuVerbiage.APP_MENU_OPTION_ADD_TO_HOMESCREEN; 783 } else if (installStrings.titleTextId == AppBannerManager.PWA_PAIR.titleTextId) { 784 mAddAppTitleShown = AppMenuVerbiage.APP_MENU_OPTION_INSTALL; 785 } 786 } 787 } 788 789 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) getAddToHomeScreenTitle(Tab currentTab)790 public AppBannerManager.InstallStringPair getAddToHomeScreenTitle(Tab currentTab) { 791 return AppBannerManager.getHomescreenLanguageOption(currentTab); 792 } 793 794 @Override getBundleForMenuItem(MenuItem item)795 public Bundle getBundleForMenuItem(MenuItem item) { 796 Bundle bundle = new Bundle(); 797 if (item.getItemId() == R.id.add_to_homescreen_id 798 || item.getItemId() == R.id.add_to_homescreen_menu_id 799 || item.getItemId() == R.id.install_app_id) { 800 bundle.putInt(AppBannerManager.MENU_TITLE_KEY, mAddAppTitleShown); 801 } 802 return bundle; 803 } 804 805 /** 806 * Sets the visibility of the "Translate" menu item. 807 */ prepareTranslateMenuItem(Menu menu, Tab currentTab)808 protected void prepareTranslateMenuItem(Menu menu, Tab currentTab) { 809 boolean isTranslateVisible = shouldShowTranslateMenuItem(currentTab); 810 menu.findItem(R.id.translate_id).setVisible(isTranslateVisible); 811 } 812 813 @Override loadingStateChanged(boolean isLoading)814 public void loadingStateChanged(boolean isLoading) { 815 if (mReloadMenuItem != null) { 816 Resources resources = mContext.getResources(); 817 mReloadMenuItem.getIcon().setLevel(isLoading 818 ? resources.getInteger(R.integer.reload_button_level_stop) 819 : resources.getInteger(R.integer.reload_button_level_reload)); 820 mReloadMenuItem.setTitle(isLoading ? R.string.accessibility_btn_stop_loading 821 : R.string.accessibility_btn_refresh); 822 mReloadMenuItem.setTitleCondensed(resources.getString( 823 isLoading ? R.string.menu_stop_refresh : R.string.menu_refresh)); 824 } 825 } 826 827 @Override onMenuDismissed()828 public void onMenuDismissed() { 829 mReloadMenuItem = null; 830 if (mUpdateMenuItemVisible) { 831 UpdateMenuItemHelper.getInstance().onMenuDismissed(); 832 UpdateMenuItemHelper.getInstance().unregisterObserver(mAppMenuInvalidator); 833 mUpdateMenuItemVisible = false; 834 mAppMenuInvalidator = null; 835 } 836 } 837 838 @VisibleForTesting shouldShowIconRow()839 boolean shouldShowIconRow() { 840 boolean shouldShowIconRow = !mIsTablet 841 || mDecorView.getWidth() 842 < DeviceFormFactor.getNonMultiDisplayMinimumTabletWidthPx(mContext); 843 844 final boolean isMenuButtonOnTop = mToolbarManager != null; 845 shouldShowIconRow &= isMenuButtonOnTop; 846 return shouldShowIconRow; 847 } 848 849 @Override 850 public int getFooterResourceId() { 851 return 0; 852 } 853 854 @Override 855 public int getHeaderResourceId() { 856 return 0; 857 } 858 859 @Override 860 public int getGroupDividerId() { 861 return R.id.divider_line_id; 862 } 863 864 @Override 865 public boolean shouldShowFooter(int maxMenuHeight) { 866 return true; 867 } 868 869 @Override 870 public boolean shouldShowHeader(int maxMenuHeight) { 871 return true; 872 } 873 874 @Override 875 public void onFooterViewInflated(AppMenuHandler appMenuHandler, View view) {} 876 877 @Override 878 public void onHeaderViewInflated(AppMenuHandler appMenuHandler, View view) {} 879 880 @Override 881 public boolean shouldShowIconBeforeItem() { 882 return false; 883 } 884 885 /** 886 * Updates the bookmark item's visibility. 887 * 888 * @param bookmarkMenuItem {@link MenuItem} for adding/editing the bookmark. 889 * @param currentTab Current tab being displayed. 890 */ 891 protected void updateBookmarkMenuItem(MenuItem bookmarkMenuItem, Tab currentTab) { 892 // If this method is called before the {@link #mBookmarkBridgeSupplierCallback} has been 893 // called, try to retrieve the bridge directly from the supplier. 894 if (mBookmarkBridge == null && mBookmarkBridgeSupplier != null) { 895 mBookmarkBridge = mBookmarkBridgeSupplier.get(); 896 } 897 898 if (mBookmarkBridge == null) { 899 // If the BookmarkBridge still isn't available, assume the bookmark menu item is not 900 // editable. 901 bookmarkMenuItem.setEnabled(false); 902 } else { 903 bookmarkMenuItem.setEnabled(mBookmarkBridge.isEditBookmarksEnabled()); 904 } 905 906 if (shouldCheckBookmarkStar(currentTab)) { 907 bookmarkMenuItem.setIcon(R.drawable.btn_star_filled); 908 bookmarkMenuItem.setChecked(true); 909 bookmarkMenuItem.setTitleCondensed(mContext.getString(R.string.edit_bookmark)); 910 } else { 911 bookmarkMenuItem.setIcon(R.drawable.btn_star); 912 bookmarkMenuItem.setChecked(false); 913 bookmarkMenuItem.setTitleCondensed(mContext.getString(R.string.menu_bookmark)); 914 } 915 } 916 917 /** 918 * Updates the request desktop site item's state. 919 * 920 * @param menu {@link Menu} for request desktop site. 921 * @param currentTab Current tab being displayed. 922 */ 923 protected void updateRequestDesktopSiteMenuItem( 924 Menu menu, Tab currentTab, boolean canShowRequestDesktopSite) { 925 MenuItem requestMenuRow = menu.findItem(R.id.request_desktop_site_row_menu_id); 926 MenuItem requestMenuLabel = menu.findItem(R.id.request_desktop_site_id); 927 MenuItem requestMenuCheck = menu.findItem(R.id.request_desktop_site_check_id); 928 929 // Hide request desktop site on all chrome:// pages except for the NTP. 930 String url = currentTab.getUrlString(); 931 boolean isChromeScheme = url.startsWith(UrlConstants.CHROME_URL_PREFIX) 932 || url.startsWith(UrlConstants.CHROME_NATIVE_URL_PREFIX); 933 934 boolean itemVisible = canShowRequestDesktopSite 935 && (!isChromeScheme || currentTab.isNativePage()) 936 && !shouldShowReaderModePrefs(currentTab) && currentTab.getWebContents() != null; 937 requestMenuRow.setVisible(itemVisible); 938 if (!itemVisible) return; 939 940 boolean isRequestDesktopSite = 941 currentTab.getWebContents().getNavigationController().getUseDesktopUserAgent(); 942 // Mark the checkbox if RDS is activated on this page. 943 requestMenuCheck.setChecked(isRequestDesktopSite); 944 945 // This title doesn't seem to be displayed by Android, but it is used to set up 946 // accessibility text in {@link AppMenuAdapter#setupMenuButton}. 947 requestMenuLabel.setTitleCondensed(isRequestDesktopSite 948 ? mContext.getString(R.string.menu_request_desktop_site_on) 949 : mContext.getString(R.string.menu_request_desktop_site_off)); 950 } 951 952 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 953 public boolean isIncognitoEnabled() { 954 return IncognitoUtils.isIncognitoModeEnabled(); 955 } 956 957 @VisibleForTesting 958 static void setPageBookmarkedForTesting(Boolean bookmarked) { 959 sItemBookmarkedForTesting = bookmarked; 960 } 961 962 private boolean shouldShowRegroupedMenu() { 963 return CachedFeatureFlags.isEnabled(ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_REGROUP); 964 } 965 966 private static boolean shouldShowThreeButtonActionBar() { 967 return CachedFeatureFlags.isEnabled( 968 ChromeFeatureList.TABBED_APP_OVERFLOW_MENU_THREE_BUTTON_ACTIONBAR); 969 } 970 971 private boolean shouldShowShareInMenu() { 972 return getActionBarType() != ActionBarType.SHARE_BUTTON; 973 } 974 975 private boolean shouldShowInfoInMenu() { 976 return getActionBarType() != ActionBarType.STANDARD; 977 } 978 979 /** 980 * @return The type of action bar should be shown. 981 */ 982 private @ActionBarType int getActionBarType() { 983 if (shouldShowRegroupedMenu()) { 984 if (ACTION_BAR_VARIATION.getValue().equals("backward_button")) { 985 return ActionBarType.BACKWARD_BUTTON; 986 } else if (ACTION_BAR_VARIATION.getValue().equals("share_button")) { 987 return ActionBarType.SHARE_BUTTON; 988 } 989 } 990 return ActionBarType.STANDARD; 991 } 992 993 /** 994 * @return The type of three button action bar should be shown. 995 */ 996 private static @ThreeButtonActionBarType int getThreeButtonActionBarType() { 997 if (shouldShowThreeButtonActionBar()) { 998 if (THREE_BUTTON_ACTION_BAR_VARIATION.getValue().equals("action_chip_view")) { 999 return ThreeButtonActionBarType.ACTION_CHIP_VIEW; 1000 } else if (THREE_BUTTON_ACTION_BAR_VARIATION.getValue().equals( 1001 "destination_chip_view")) { 1002 return ThreeButtonActionBarType.DESTINATION_CHIP_VIEW; 1003 } else if (THREE_BUTTON_ACTION_BAR_VARIATION.getValue().equals("add_to_option")) { 1004 return ThreeButtonActionBarType.ADD_TO_OPTION; 1005 } 1006 } 1007 return ThreeButtonActionBarType.DISABLED; 1008 } 1009 1010 /** 1011 * @return The "download" menu items id in the app menu. 1012 */ 1013 public static int getOfflinePageId() { 1014 @ThreeButtonActionBarType 1015 int type = getThreeButtonActionBarType(); 1016 if (type == ThreeButtonActionBarType.ACTION_CHIP_VIEW 1017 || type == ThreeButtonActionBarType.DESTINATION_CHIP_VIEW) { 1018 return R.id.offline_page_chip_id; 1019 } else if (type == ThreeButtonActionBarType.ADD_TO_OPTION) { 1020 return R.id.add_to_downloads_menu_id; 1021 } 1022 return R.id.offline_page_id; 1023 } 1024 1025 /** 1026 * @return The "Add to Home screen" menu items id in the app menu. 1027 */ 1028 public static int getAddToHomescreenId() { 1029 if (getThreeButtonActionBarType() == ThreeButtonActionBarType.ADD_TO_OPTION) { 1030 return R.id.add_to_homescreen_menu_id; 1031 } 1032 return R.id.add_to_homescreen_id; 1033 } 1034 1035 @Override 1036 public boolean recordAppMenuSimilarSelectionIfNeeded( 1037 int previousMenuItemId, int currentMenuItemId) { 1038 @AppMenuSimilarSelectionType 1039 int pattern = findSimilarSelectionPattern(previousMenuItemId, currentMenuItemId); 1040 if (pattern == AppMenuSimilarSelectionType.NO_MATCH) { 1041 return false; 1042 } 1043 1044 RecordHistogram.recordEnumeratedHistogram("Mobile.AppMenu.SimilarSelection", pattern, 1045 AppMenuSimilarSelectionType.NUM_ENTRIES); 1046 return true; 1047 } 1048 1049 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 1050 public @AppMenuSimilarSelectionType int findSimilarSelectionPattern( 1051 int previousMenuItemId, int currentMenuItemId) { 1052 Pair<Set<Integer>, Integer> menuItemToSelectType = 1053 sSimilarSelectedMenuItemMap.get(currentMenuItemId); 1054 if (menuItemToSelectType != null 1055 && menuItemToSelectType.first.contains(previousMenuItemId)) { 1056 return menuItemToSelectType.second; 1057 } 1058 1059 return AppMenuSimilarSelectionType.NO_MATCH; 1060 } 1061 1062 private static Map<Integer, Pair<Set<Integer>, Integer>> createSimilarSelectedMap() { 1063 Map<Integer, Pair<Set<Integer>, Integer>> map = new LinkedHashMap<>(); 1064 map.put(R.id.all_bookmarks_menu_id, 1065 new Pair<Set<Integer>, Integer>( 1066 new HashSet<>(Arrays.asList(R.id.bookmark_this_page_id, 1067 R.id.bookmark_this_page_chip_id, R.id.add_to_bookmarks_menu_id)), 1068 AppMenuSimilarSelectionType.BOOKMARK_PAGE_THEN_ALL_BOOKMARKS)); 1069 map.put(R.id.bookmark_this_page_id, 1070 new Pair<Set<Integer>, Integer>( 1071 new HashSet<>(Arrays.asList(R.id.all_bookmarks_menu_id)), 1072 AppMenuSimilarSelectionType.ALL_BOOKMARKS_THEN_BOOKMARK_PAGE)); 1073 map.put(R.id.bookmark_this_page_chip_id, 1074 new Pair<Set<Integer>, Integer>( 1075 new HashSet<>(Arrays.asList(R.id.all_bookmarks_menu_id)), 1076 AppMenuSimilarSelectionType.ALL_BOOKMARKS_THEN_BOOKMARK_PAGE)); 1077 map.put(R.id.add_to_bookmarks_menu_id, 1078 new Pair<Set<Integer>, Integer>( 1079 new HashSet<>(Arrays.asList(R.id.all_bookmarks_menu_id)), 1080 AppMenuSimilarSelectionType.ALL_BOOKMARKS_THEN_BOOKMARK_PAGE)); 1081 map.put(R.id.downloads_menu_id, 1082 new Pair<Set<Integer>, Integer>( 1083 new HashSet<>(Arrays.asList(R.id.offline_page_id, R.id.offline_page_chip_id, 1084 R.id.add_to_downloads_menu_id)), 1085 AppMenuSimilarSelectionType.DOWNLOAD_PAGE_THEN_ALL_DOWNLOADS)); 1086 map.put(R.id.offline_page_id, 1087 new Pair<Set<Integer>, Integer>( 1088 new HashSet<>(Arrays.asList(R.id.downloads_menu_id)), 1089 AppMenuSimilarSelectionType.ALL_DOWNLOADS_THEN_DOWNLOAD_PAGE)); 1090 map.put(R.id.offline_page_chip_id, 1091 new Pair<Set<Integer>, Integer>( 1092 new HashSet<>(Arrays.asList(R.id.downloads_menu_id)), 1093 AppMenuSimilarSelectionType.ALL_DOWNLOADS_THEN_DOWNLOAD_PAGE)); 1094 map.put(R.id.add_to_downloads_menu_id, 1095 new Pair<Set<Integer>, Integer>( 1096 new HashSet<>(Arrays.asList(R.id.downloads_menu_id)), 1097 AppMenuSimilarSelectionType.ALL_DOWNLOADS_THEN_DOWNLOAD_PAGE)); 1098 return map; 1099 } 1100 } 1101