// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.toolbar.top;
import static org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils.buildMenuListItem;
import static org.chromium.ui.listmenu.BasicListMenu.buildMenuDivider;
import android.content.Context;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.ListView;
import android.widget.PopupWindow;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.incognito.IncognitoUtils;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.MenuBuilderHelper;
import org.chromium.chrome.browser.toolbar.R;
import org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.ui.listmenu.BasicListMenu;
import org.chromium.ui.listmenu.ListMenu;
import org.chromium.ui.listmenu.ListMenuButton;
import org.chromium.ui.listmenu.ListMenuButtonDelegate;
import org.chromium.ui.listmenu.ListMenuItemProperties;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.widget.RectProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* The main coordinator for the Tab Switcher Action Menu, responsible for creating the popup menu
* (popup window) in general and building a list of menu items.
*/
public class TabSwitcherActionMenuCoordinator {
@Retention(RetentionPolicy.SOURCE)
@IntDef({
MenuItemType.DIVIDER,
MenuItemType.CLOSE_TAB,
MenuItemType.NEW_TAB,
MenuItemType.NEW_INCOGNITO_TAB,
MenuItemType.SWITCH_TO_INCOGNITO,
MenuItemType.SWITCH_OUT_OF_INCOGNITO,
MenuItemType.CLOSE_ALL_INCOGNITO_TABS
})
public @interface MenuItemType {
int DIVIDER = 0;
int CLOSE_TAB = 1;
int NEW_TAB = 2;
int NEW_INCOGNITO_TAB = 3;
int SWITCH_TO_INCOGNITO = 4;
int SWITCH_OUT_OF_INCOGNITO = 5;
int CLOSE_ALL_INCOGNITO_TABS = 6;
}
/**
* @param onItemClicked The clicked listener handling clicks on TabSwitcherActionMenu.
* @param profile The {@link Profile} associated with the tabs.
* @return a long click listener of the long press action of tab switcher button.
*/
public static OnLongClickListener createOnLongClickListener(
Callback<Integer> onItemClicked,
Profile profile,
ObservableSupplier<TabModelSelector> tabModelSelectorSupplier) {
return createOnLongClickListener(
new TabSwitcherActionMenuCoordinator(profile, tabModelSelectorSupplier),
profile,
onItemClicked);
}
// internal helper function to create a long click listener.
protected static OnLongClickListener createOnLongClickListener(
TabSwitcherActionMenuCoordinator menu,
Profile profile,
Callback<Integer> onItemClicked) {
return (view) -> {
Context context = view.getContext();
menu.displayMenu(
context,
(ListMenuButton) view,
menu.buildMenuItems(),
(id) -> {
recordUserActions(id);
onItemClicked.onResult(id);
});
TrackerFactory.getTrackerForProfile(profile)
.notifyEvent(EventConstants.TAB_SWITCHER_BUTTON_LONG_CLICKED);
return true;
};
}
private static void recordUserActions(int id) {
if (id == R.id.close_tab) {
RecordUserAction.record("MobileMenuCloseTab.LongTapMenu");
} else if (id == R.id.new_tab_menu_id) {
RecordUserAction.record("MobileMenuNewTab.LongTapMenu");
} else if (id == R.id.new_incognito_tab_menu_id) {
RecordUserAction.record("MobileMenuNewIncognitoTab.LongTapMenu");
} else if (id == R.id.close_all_incognito_tabs_menu_id) {
RecordUserAction.record("MobileMenuCloseAllIncognitoTabs.LongTapMenu");
} else if (id == R.id.switch_to_incognito_menu_id) {
RecordUserAction.record("MobileMenuSwitchToIncognito.LongTapMenu");
} else if (id == R.id.switch_out_of_incognito_menu_id) {
RecordUserAction.record("MobileMenuSwitchOutOfIncognito.LongTapMenu");
}
}
private final ObservableSupplier<TabModelSelector> mTabModelSelectorSupplier;
private final Profile mProfile;
// For test.
private View mContentView;
/** Construct a coordinator for the given {@link Profile}. */
TabSwitcherActionMenuCoordinator(
Profile profile, ObservableSupplier<TabModelSelector> tabModelSelectorSupplier) {
mProfile = profile;
mTabModelSelectorSupplier = tabModelSelectorSupplier;
}
/**
* Created and display the tab switcher action menu anchored to the specified view.
*
* @param context The context of the TabSwitcherActionMenu.
* @param anchorView The anchor {@link View} of the {@link PopupWindow}.
* @param listItems The menu item models.
* @param onItemClicked The clicked listener handling clicks on TabSwitcherActionMenu.
*/
@VisibleForTesting
void displayMenu(
final Context context,
ListMenuButton anchorView,
ModelList listItems,
Callback<Integer> onItemClicked) {
RectProvider rectProvider = MenuBuilderHelper.getRectProvider(anchorView);
BasicListMenu listMenu =
BrowserUiListMenuUtils.getBasicListMenu(
context,
listItems,
(model) -> {
onItemClicked.onResult(model.get(ListMenuItemProperties.MENU_ITEM_ID));
});
mContentView = listMenu.getContentView();
int verticalPadding =
context.getResources()
.getDimensionPixelOffset(R.dimen.tab_switcher_menu_vertical_padding);
ListView listView = listMenu.getListView();
listView.setPaddingRelative(
listView.getPaddingStart(),
verticalPadding,
listView.getPaddingEnd(),
verticalPadding);
ListMenuButtonDelegate delegate =
new ListMenuButtonDelegate() {
@Override
public ListMenu getListMenu() {
return listMenu;
}
@Override
public RectProvider getRectProvider(View listMenuButton) {
return rectProvider;
}
};
anchorView.setDelegate(delegate, false);
anchorView.showMenu();
}
@VisibleForTesting
View getContentView() {
return mContentView;
}
ModelList buildMenuItems() {
boolean isCurrentModelIncognito =
mTabModelSelectorSupplier.hasValue()
&& mTabModelSelectorSupplier.get().isIncognitoBrandedModelSelected();
boolean hasIncognitoTabs =
mTabModelSelectorSupplier.hasValue()
&& mTabModelSelectorSupplier.get().getModel(true).getCount() > 0;
boolean incognitoMigrationFFEnabled =
ChromeFeatureList.sTabStripIncognitoMigration.isEnabled();
ModelList itemList = new ModelList();
itemList.add(buildListItemByMenuItemType(MenuItemType.CLOSE_TAB));
if (incognitoMigrationFFEnabled && isCurrentModelIncognito && hasIncognitoTabs) {
itemList.add(buildListItemByMenuItemType(MenuItemType.CLOSE_ALL_INCOGNITO_TABS));
}
itemList.add(buildListItemByMenuItemType(MenuItemType.DIVIDER));
itemList.add(buildListItemByMenuItemType(MenuItemType.NEW_TAB));
itemList.add(buildListItemByMenuItemType(MenuItemType.NEW_INCOGNITO_TAB));
if (incognitoMigrationFFEnabled) {
if (isCurrentModelIncognito) {
itemList.add(buildListItemByMenuItemType(MenuItemType.SWITCH_OUT_OF_INCOGNITO));
} else if (hasIncognitoTabs) {
// Show switch into incognito when incognito model has tabs.
itemList.add(buildListItemByMenuItemType(MenuItemType.SWITCH_TO_INCOGNITO));
}
}
return itemList;
}
protected ListItem buildListItemByMenuItemType(@MenuItemType int type) {
switch (type) {
case MenuItemType.CLOSE_TAB:
return buildMenuListItem(R.string.close_tab, R.id.close_tab, R.drawable.btn_close);
case MenuItemType.NEW_TAB:
return buildMenuListItem(
R.string.menu_new_tab, R.id.new_tab_menu_id, R.drawable.new_tab_icon);
case MenuItemType.NEW_INCOGNITO_TAB:
return buildMenuListItem(
R.string.menu_new_incognito_tab,
R.id.new_incognito_tab_menu_id,
R.drawable.incognito_simple,
IncognitoUtils.isIncognitoModeEnabled(mProfile));
case MenuItemType.CLOSE_ALL_INCOGNITO_TABS:
return buildMenuListItem(
R.string.menu_close_all_incognito_tabs,
R.id.close_all_incognito_tabs_menu_id,
R.drawable.ic_close_all_tabs);
case MenuItemType.SWITCH_TO_INCOGNITO:
return buildMenuListItem(
R.string.menu_switch_to_incognito,
R.id.switch_to_incognito_menu_id,
R.drawable.ic_switch_to_incognito);
case MenuItemType.SWITCH_OUT_OF_INCOGNITO:
return buildMenuListItem(
R.string.menu_switch_out_of_incognito,
R.id.switch_out_of_incognito_menu_id,
R.drawable.ic_switch_out_of_incognito);
case MenuItemType.DIVIDER:
default:
return buildMenuDivider();
}
}
}