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