[Tab Switcher] TabSwitcherPane level TabSwitcherCoordinator elements
In the process of "forking" TabSwitcherCoordinator/Mediator some elements of TabSwitcherCoordinator are better handled at the Pane level. This CL contains the menu and keyboard and price tracking related functionality that should be managed at the pane scope. Bug: 1505772 Change-Id: Iffe1d3891d91f4947e2b0296d6df17ff9855cc92 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5131306 Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Sky Malice <skym@chromium.org> Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org> Cr-Commit-Position: refs/heads/main@{#1243476}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
7d97ef4d2e
commit
409472450f
chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management
@ -10,13 +10,13 @@ import android.view.View.OnClickListener;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.chromium.base.supplier.Supplier;
|
||||
import org.chromium.chrome.browser.hub.LoadHint;
|
||||
import org.chromium.chrome.browser.hub.Pane;
|
||||
import org.chromium.chrome.browser.hub.PaneId;
|
||||
import org.chromium.chrome.browser.hub.ResourceButtonData;
|
||||
import org.chromium.chrome.browser.tabmodel.IncognitoTabModel;
|
||||
import org.chromium.chrome.browser.tabmodel.IncognitoTabModelObserver;
|
||||
import org.chromium.chrome.tab_ui.R;
|
||||
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
|
||||
|
||||
/** A {@link Pane} representing the incognito tab switcher. */
|
||||
public class IncognitoTabSwitcherPane extends TabSwitcherPaneBase {
|
||||
@ -44,16 +44,19 @@ public class IncognitoTabSwitcherPane extends TabSwitcherPaneBase {
|
||||
* @param incognitoTabModelSupplier The supplier of the incognito tab model. Returns a valid
|
||||
* model once native is loaded and will never change thereafter.
|
||||
* @param newTabButtonClickListener The {@link OnClickListener} for the new tab button.
|
||||
* @param menuOrKeyboardActionController allows access to menu or keyboard actions.
|
||||
*/
|
||||
IncognitoTabSwitcherPane(
|
||||
@NonNull Context context,
|
||||
@NonNull TabSwitcherPaneCoordinatorFactory factory,
|
||||
@NonNull Supplier<IncognitoTabModel> incognitoTabModelSupplier,
|
||||
@NonNull OnClickListener newTabButtonClickListener) {
|
||||
@NonNull OnClickListener newTabButtonClickListener,
|
||||
@NonNull MenuOrKeyboardActionController menuOrKeyboardActionController) {
|
||||
super(
|
||||
context,
|
||||
factory,
|
||||
newTabButtonClickListener,
|
||||
menuOrKeyboardActionController,
|
||||
org.chromium.chrome.browser.toolbar.R.string.button_new_incognito_tab);
|
||||
|
||||
mIncognitoTabModelSupplier = incognitoTabModelSupplier;
|
||||
@ -90,9 +93,4 @@ public class IncognitoTabSwitcherPane extends TabSwitcherPaneBase {
|
||||
public @PaneId int getPaneId() {
|
||||
return PaneId.INCOGNITO_TAB_SWITCHER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyLoadHint(@LoadHint int loadHint) {
|
||||
// TODO(crbug/1505772): Implement.
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,10 @@ import org.mockito.junit.MockitoRule;
|
||||
import org.chromium.base.test.BaseRobolectricTestRunner;
|
||||
import org.chromium.chrome.browser.hub.DisplayButtonData;
|
||||
import org.chromium.chrome.browser.hub.FullButtonData;
|
||||
import org.chromium.chrome.browser.hub.LoadHint;
|
||||
import org.chromium.chrome.browser.hub.PaneId;
|
||||
import org.chromium.chrome.browser.tabmodel.IncognitoTabModel;
|
||||
import org.chromium.chrome.browser.tabmodel.IncognitoTabModelObserver;
|
||||
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link IncognitoTabSwitcherPane}. Refer to {@link TabSwitcherPaneUnitTest} for
|
||||
@ -53,6 +53,7 @@ public class IncognitoTabSwitcherPaneUnitTest {
|
||||
@Mock private TabSwitcherPaneCoordinator mTabSwitcherPaneCoordinator;
|
||||
@Mock private View.OnClickListener mNewTabButtonClickListener;
|
||||
@Mock private IncognitoTabModel mIncognitoTabModel;
|
||||
@Mock private MenuOrKeyboardActionController mMenuOrKeyboardActionController;
|
||||
|
||||
@Captor private ArgumentCaptor<IncognitoTabModelObserver> mIncognitoTabModelObserverCaptor;
|
||||
|
||||
@ -76,7 +77,8 @@ public class IncognitoTabSwitcherPaneUnitTest {
|
||||
mContext,
|
||||
mTabSwitcherPaneCoordinatorFactory,
|
||||
() -> mIncognitoTabModel,
|
||||
mNewTabButtonClickListener);
|
||||
mNewTabButtonClickListener,
|
||||
mMenuOrKeyboardActionController);
|
||||
}
|
||||
|
||||
@After
|
||||
@ -116,15 +118,6 @@ public class IncognitoTabSwitcherPaneUnitTest {
|
||||
assertEquals(PaneId.INCOGNITO_TAB_SWITCHER, mIncognitoTabSwitcherPane.getPaneId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testLoadHint() {
|
||||
// TODO(crbug/1505772): this is a noop right now.
|
||||
mIncognitoTabSwitcherPane.notifyLoadHint(LoadHint.COLD);
|
||||
mIncognitoTabSwitcherPane.notifyLoadHint(LoadHint.WARM);
|
||||
mIncognitoTabSwitcherPane.notifyLoadHint(LoadHint.HOT);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testNewTabButton() {
|
||||
|
@ -65,7 +65,7 @@ public class TabListCoordinator
|
||||
*
|
||||
* <p>NOTE: STRIP, LIST, and GRID modes will have height equal to that of the container view.
|
||||
*/
|
||||
@IntDef({TabListMode.GRID, TabListMode.STRIP, TabListMode.LIST})
|
||||
@IntDef({TabListMode.GRID, TabListMode.STRIP, TabListMode.LIST, TabListMode.NUM_ENTRIES})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TabListMode {
|
||||
int GRID = 0;
|
||||
|
@ -28,6 +28,7 @@ import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
|
||||
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
|
||||
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
|
||||
import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
|
||||
import org.chromium.chrome.browser.profiles.ProfileProvider;
|
||||
import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider;
|
||||
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
|
||||
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
|
||||
@ -165,13 +166,17 @@ public interface TabManagementDelegate {
|
||||
* Create a {@link TabSwitcher} and {@link Pane} for the Hub.
|
||||
*
|
||||
* @param activity The {@link Activity} that hosts the pane.
|
||||
* @param profileProviderSupplier The supplier for profiles.
|
||||
* @param tabModelSelector For access to {@link TabModel}.
|
||||
* @param newTabButtonOnClickListener The listener for clicking the new tab button.
|
||||
* @param menuOrKeyboardActionController Allows access to menu or keyboard actions.
|
||||
* @param isIncognito Whether this is an incognito pane.
|
||||
*/
|
||||
Pair<TabSwitcher, Pane> createTabSwitcherPane(
|
||||
@NonNull Activity activity,
|
||||
@NonNull OneshotSupplier<ProfileProvider> profileProviderSupplier,
|
||||
@NonNull TabModelSelector tabModelSelector,
|
||||
@NonNull OnClickListener newTabButtonOnClickListener,
|
||||
@NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
|
||||
boolean isIncognito);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.chromium.base.ContextUtils;
|
||||
import org.chromium.base.supplier.ObservableSupplier;
|
||||
import org.chromium.base.supplier.OneshotSupplier;
|
||||
import org.chromium.base.supplier.Supplier;
|
||||
@ -27,9 +28,11 @@ import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
|
||||
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
|
||||
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
|
||||
import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
|
||||
import org.chromium.chrome.browser.profiles.ProfileProvider;
|
||||
import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider;
|
||||
import org.chromium.chrome.browser.tabmodel.IncognitoTabModel;
|
||||
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
|
||||
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
|
||||
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
|
||||
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
|
||||
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
|
||||
@ -143,8 +146,10 @@ public class TabManagementDelegateImpl implements TabManagementDelegate {
|
||||
@Override
|
||||
public Pair<TabSwitcher, Pane> createTabSwitcherPane(
|
||||
@NonNull Activity activity,
|
||||
@NonNull OneshotSupplier<ProfileProvider> profileProviderSupplier,
|
||||
@NonNull TabModelSelector tabModelSelector,
|
||||
@NonNull OnClickListener newTabButtonOnClickListener,
|
||||
@NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
|
||||
boolean isIncognito) {
|
||||
// TODO(crbug/1505772): Consider making this an activity scoped singleton and possibly
|
||||
// hosting it in CTA/HubProvider.
|
||||
@ -156,13 +161,20 @@ public class TabManagementDelegateImpl implements TabManagementDelegate {
|
||||
activity,
|
||||
factory,
|
||||
() -> (IncognitoTabModel) tabModelSelector.getModel(true),
|
||||
newTabButtonOnClickListener);
|
||||
newTabButtonOnClickListener,
|
||||
menuOrKeyboardActionController);
|
||||
} else {
|
||||
Supplier<TabModelFilter> tabModelFilterSupplier =
|
||||
() -> tabModelSelector.getTabModelFilterProvider().getTabModelFilter(false);
|
||||
pane =
|
||||
new TabSwitcherPane(
|
||||
activity,
|
||||
ContextUtils.getAppSharedPreferences(),
|
||||
profileProviderSupplier,
|
||||
factory,
|
||||
tabModelFilterSupplier,
|
||||
newTabButtonOnClickListener,
|
||||
menuOrKeyboardActionController,
|
||||
new TabSwitcherPaneDrawableCoordinator(activity, tabModelSelector));
|
||||
}
|
||||
return Pair.create(new TabSwitcherPaneAdapter(pane), pane);
|
||||
|
@ -5,50 +5,84 @@
|
||||
package org.chromium.chrome.browser.tasks.tab_management;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.chromium.base.supplier.OneshotSupplier;
|
||||
import org.chromium.base.supplier.Supplier;
|
||||
import org.chromium.chrome.browser.hub.DrawableButtonData;
|
||||
import org.chromium.chrome.browser.hub.LoadHint;
|
||||
import org.chromium.chrome.browser.hub.Pane;
|
||||
import org.chromium.chrome.browser.hub.PaneId;
|
||||
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
|
||||
import org.chromium.chrome.browser.price_tracking.PriceTrackingUtilities;
|
||||
import org.chromium.chrome.browser.profiles.ProfileProvider;
|
||||
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
|
||||
import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
|
||||
import org.chromium.chrome.tab_ui.R;
|
||||
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
|
||||
|
||||
/** A {@link Pane} representing the regular tab switcher. */
|
||||
public class TabSwitcherPane extends TabSwitcherPaneBase {
|
||||
private final @NonNull SharedPreferences mSharedPreferences;
|
||||
private final @NonNull Supplier<TabModelFilter> mTabModelFilterSupplier;
|
||||
private final @NonNull TabSwitcherPaneDrawableCoordinator mTabSwitcherPaneDrawableCoordinator;
|
||||
|
||||
private @Nullable OnSharedPreferenceChangeListener mPriceAnnotationsPrefListener;
|
||||
private boolean mIsVisible;
|
||||
|
||||
/**
|
||||
* @param context The activity context.
|
||||
* @param sharedPreferences The app shared preferences.
|
||||
* @param profileProviderSupplier The profile provider supplier.
|
||||
* @param factory The factory used to construct {@link TabSwitcherPaneCoordinator}s.
|
||||
* @param tabModelFilterSupplier The supplier of the regular {@link TabModelFilter}.
|
||||
* @param newTabButtonClickListener The {@link OnClickListener} for the new tab button.
|
||||
* @param menuOrKeyboardActionController Allows access to menu or keyboard actions.
|
||||
* @param tabSwitcherPaneDrawableCoordinator The drawable to represent the pane.
|
||||
*/
|
||||
TabSwitcherPane(
|
||||
@NonNull Context context,
|
||||
@NonNull SharedPreferences sharedPreferences,
|
||||
@NonNull OneshotSupplier<ProfileProvider> profileProviderSupplier,
|
||||
@NonNull TabSwitcherPaneCoordinatorFactory factory,
|
||||
@NonNull Supplier<TabModelFilter> tabModelFilterSupplier,
|
||||
@NonNull OnClickListener newTabButtonClickListener,
|
||||
@NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
|
||||
@NonNull TabSwitcherPaneDrawableCoordinator tabSwitcherDrawableCoordinator) {
|
||||
super(
|
||||
context,
|
||||
factory,
|
||||
newTabButtonClickListener,
|
||||
menuOrKeyboardActionController,
|
||||
org.chromium.chrome.browser.toolbar.R.string.button_new_tab);
|
||||
mSharedPreferences = sharedPreferences;
|
||||
mTabModelFilterSupplier = tabModelFilterSupplier;
|
||||
mTabSwitcherPaneDrawableCoordinator = tabSwitcherDrawableCoordinator;
|
||||
|
||||
// TODO(crbug/1505772): Update this string to not be an a11y string and it should probably
|
||||
// just say "Tabs".
|
||||
mReferenceButtonDataSupplier.set(
|
||||
new DrawableButtonData(
|
||||
org.chromium.chrome.tab_ui.R.string.accessibility_tab_switcher,
|
||||
org.chromium.chrome.tab_ui.R.string.accessibility_tab_switcher,
|
||||
R.string.accessibility_tab_switcher,
|
||||
R.string.accessibility_tab_switcher,
|
||||
tabSwitcherDrawableCoordinator.getTabSwitcherDrawable()));
|
||||
|
||||
profileProviderSupplier.onAvailable(this::onProfileProviderAvailable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
mTabSwitcherPaneDrawableCoordinator.destroy();
|
||||
if (mPriceAnnotationsPrefListener != null) {
|
||||
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(
|
||||
mPriceAnnotationsPrefListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -58,6 +92,29 @@ public class TabSwitcherPane extends TabSwitcherPaneBase {
|
||||
|
||||
@Override
|
||||
public void notifyLoadHint(@LoadHint int loadHint) {
|
||||
// TODO(crbug/1505772): Implement.
|
||||
super.notifyLoadHint(loadHint);
|
||||
mIsVisible = loadHint == LoadHint.HOT;
|
||||
}
|
||||
|
||||
private void onProfileProviderAvailable(@NonNull ProfileProvider profileProvider) {
|
||||
if (!PriceTrackingFeatures.isPriceTrackingEnabled(profileProvider.getOriginalProfile())
|
||||
&& getTabListMode() == TabListMode.GRID) {
|
||||
return;
|
||||
}
|
||||
mPriceAnnotationsPrefListener =
|
||||
(sharedPrefs, key) -> {
|
||||
if (!PriceTrackingUtilities.TRACK_PRICES_ON_TABS.equals(key) || !mIsVisible) {
|
||||
return;
|
||||
}
|
||||
TabModelFilter filter = mTabModelFilterSupplier.get();
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator = getTabSwitcherPaneCoordinator();
|
||||
if (filter.isCurrentlySelectedFilter()
|
||||
&& filter.isTabModelRestored()
|
||||
&& coordinator != null) {
|
||||
coordinator.resetWithTabList(filter);
|
||||
}
|
||||
};
|
||||
mSharedPreferences.registerOnSharedPreferenceChangeListener(mPriceAnnotationsPrefListener);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.chromium.base.metrics.RecordUserAction;
|
||||
import org.chromium.base.supplier.ObservableSupplier;
|
||||
import org.chromium.base.supplier.ObservableSupplierImpl;
|
||||
import org.chromium.base.supplier.Supplier;
|
||||
@ -26,8 +27,13 @@ import org.chromium.chrome.browser.hub.FullButtonData;
|
||||
import org.chromium.chrome.browser.hub.HubContainerView;
|
||||
import org.chromium.chrome.browser.hub.HubLayoutAnimatorProvider;
|
||||
import org.chromium.chrome.browser.hub.HubLayoutConstants;
|
||||
import org.chromium.chrome.browser.hub.LoadHint;
|
||||
import org.chromium.chrome.browser.hub.Pane;
|
||||
import org.chromium.chrome.browser.hub.ResourceButtonData;
|
||||
import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
|
||||
import org.chromium.chrome.tab_ui.R;
|
||||
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
|
||||
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController.MenuOrKeyboardActionHandler;
|
||||
import org.chromium.ui.base.DeviceFormFactor;
|
||||
|
||||
/**
|
||||
@ -39,6 +45,24 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
new ObservableSupplierImpl<>();
|
||||
protected final ObservableSupplierImpl<FullButtonData> mNewTabButtonDataSupplier =
|
||||
new ObservableSupplierImpl<>();
|
||||
|
||||
private final MenuOrKeyboardActionHandler mMenuOrKeyboardActionHandler =
|
||||
new MenuOrKeyboardActionHandler() {
|
||||
@Override
|
||||
public boolean handleMenuOrKeyboardAction(int id, boolean fromMenu) {
|
||||
if (id == R.id.menu_select_tabs) {
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator =
|
||||
mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
if (coordinator == null) return false;
|
||||
|
||||
coordinator.showTabListEditor();
|
||||
RecordUserAction.record("MobileMenuSelectTabs");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
private final ObservableSupplierImpl<TabSwitcherPaneCoordinator>
|
||||
mTabSwitcherPaneCoordinatorSupplier = new ObservableSupplierImpl<>();
|
||||
private final TransitiveObservableSupplier<TabSwitcherPaneCoordinator, Boolean>
|
||||
@ -47,6 +71,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
mTabSwitcherPaneCoordinatorSupplier,
|
||||
pc -> pc.getHandleBackPressChangedSupplier());
|
||||
private final ViewGroup mRootView;
|
||||
private final MenuOrKeyboardActionController mMenuOrKeyboardActionController;
|
||||
private final TabSwitcherPaneCoordinatorFactory mFactory;
|
||||
|
||||
private boolean mNativeInitialized;
|
||||
@ -56,6 +81,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
* @param context The activity context.
|
||||
* @param factory The factory used to construct {@link TabSwitcherPaneCoordinator}s.
|
||||
* @param newTabButtonClickListener The {@link OnClickListener} for the new tab button.
|
||||
* @param menuOrKeyboardActionController Allows access to menu or keyboard actions.
|
||||
* @param newTabButtonContentDescriptionRes The resource for the new tab button content
|
||||
* description.
|
||||
*/
|
||||
@ -63,8 +89,10 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
@NonNull Context context,
|
||||
@NonNull TabSwitcherPaneCoordinatorFactory factory,
|
||||
@NonNull OnClickListener newTabButtonClickListener,
|
||||
@NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
|
||||
@StringRes int newTabButtonContentDescriptionRes) {
|
||||
mFactory = factory;
|
||||
mMenuOrKeyboardActionController = menuOrKeyboardActionController;
|
||||
|
||||
mNewTabButtonDataSupplier.set(
|
||||
new DelegateButtonData(
|
||||
@ -79,6 +107,8 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
mMenuOrKeyboardActionController.unregisterMenuOrKeyboardActionHandler(
|
||||
mMenuOrKeyboardActionHandler);
|
||||
destroyTabSwitcherPaneCoordinator();
|
||||
}
|
||||
|
||||
@ -87,6 +117,21 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyLoadHint(@LoadHint int loadHint) {
|
||||
// TODO(crbug/1502201): Figure out a more immediate signal for pane visibility. Due to
|
||||
// WARM/COLD signals being posted this can lead to multiple HOT panes for a brief period.
|
||||
// In this case multiple HOT panes might listen for the same menu event leading to a
|
||||
// collision.
|
||||
if (loadHint == LoadHint.HOT) {
|
||||
mMenuOrKeyboardActionController.registerMenuOrKeyboardActionHandler(
|
||||
mMenuOrKeyboardActionHandler);
|
||||
} else {
|
||||
mMenuOrKeyboardActionController.unregisterMenuOrKeyboardActionHandler(
|
||||
mMenuOrKeyboardActionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull ObservableSupplier<FullButtonData> getActionButtonDataSupplier() {
|
||||
return mNewTabButtonDataSupplier;
|
||||
@ -117,6 +162,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
|
||||
@Override
|
||||
public @BackPressResult int handleBackPress() {
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
if (coordinator == null) return BackPressResult.FAILURE;
|
||||
return coordinator.handleBackPress();
|
||||
@ -131,6 +177,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
if (mNativeInitialized) return;
|
||||
|
||||
mNativeInitialized = true;
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
if (coordinator != null) {
|
||||
coordinator.initWithNative();
|
||||
@ -139,6 +186,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
|
||||
/** Returns a {@link Supplier} that provides dialog visibility information. */
|
||||
public @Nullable Supplier<Boolean> getTabGridDialogVisibilitySupplier() {
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
if (coordinator == null) return null;
|
||||
return coordinator.getTabGridDialogVisibilitySupplier();
|
||||
@ -146,6 +194,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
|
||||
/** Returns a {@link TabSwitcherCustomViewManager} for supplying custom views. */
|
||||
public @Nullable TabSwitcherCustomViewManager getTabSwitcherCustomViewManager() {
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
if (coordinator == null) return null;
|
||||
return coordinator.getTabSwitcherCustomViewManager();
|
||||
@ -153,6 +202,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
|
||||
/** Returns the number of elements in the tab switcher's tab list model. */
|
||||
public int getTabSwitcherTabListModelSize() {
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
if (coordinator == null) return 0;
|
||||
return coordinator.getTabSwitcherTabListModelSize();
|
||||
@ -160,11 +210,17 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
|
||||
/** Set the tab switcher's RecyclerViewPosition. */
|
||||
public void setTabSwitcherRecyclerViewPosition(RecyclerViewPosition position) {
|
||||
@Nullable
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
if (coordinator == null) return;
|
||||
coordinator.setTabSwitcherRecyclerViewPosition(position);
|
||||
}
|
||||
|
||||
protected @TabListMode int getTabListMode() {
|
||||
return mFactory.getTabListMode();
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
|
||||
protected @Nullable TabSwitcherPaneCoordinator getTabSwitcherPaneCoordinator() {
|
||||
return mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
}
|
||||
@ -173,7 +229,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
void createTabSwitcherPaneCoordinator() {
|
||||
if (mTabSwitcherPaneCoordinatorSupplier.hasValue()) return;
|
||||
|
||||
TabSwitcherPaneCoordinator coordinator = mFactory.create(mRootView);
|
||||
@NonNull TabSwitcherPaneCoordinator coordinator = mFactory.create(mRootView);
|
||||
mTabSwitcherPaneCoordinatorSupplier.set(coordinator);
|
||||
if (mNativeInitialized) {
|
||||
coordinator.initWithNative();
|
||||
@ -183,7 +239,7 @@ public abstract class TabSwitcherPaneBase implements Pane {
|
||||
protected void destroyTabSwitcherPaneCoordinator() {
|
||||
if (!mTabSwitcherPaneCoordinatorSupplier.hasValue()) return;
|
||||
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
@NonNull TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
|
||||
mTabSwitcherPaneCoordinatorSupplier.set(null);
|
||||
mRootView.removeAllViews();
|
||||
coordinator.destroy();
|
||||
|
@ -11,6 +11,7 @@ import androidx.annotation.Nullable;
|
||||
import org.chromium.base.supplier.ObservableSupplier;
|
||||
import org.chromium.base.supplier.ObservableSupplierImpl;
|
||||
import org.chromium.base.supplier.Supplier;
|
||||
import org.chromium.chrome.browser.tabmodel.TabList;
|
||||
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
|
||||
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.BackPressResult;
|
||||
|
||||
@ -29,6 +30,20 @@ public class TabSwitcherPaneCoordinator implements BackPressHandler {
|
||||
/** Post native initialization. */
|
||||
public void initWithNative() {}
|
||||
|
||||
/** Shows the tab list editor. */
|
||||
public void showTabListEditor() {
|
||||
assert false : "Not implemented.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the UI with the specified tabs.
|
||||
*
|
||||
* @param tabList The {@link TabList} to show tabs for.
|
||||
*/
|
||||
public void resetWithTabList(@Nullable TabList tabList) {
|
||||
assert false : "Not implemented.";
|
||||
}
|
||||
|
||||
/** Returns a {@link Supplier} that provides dialog visibility information. */
|
||||
public @Nullable Supplier<Boolean> getTabGridDialogVisibilitySupplier() {
|
||||
assert false : "Not implemented.";
|
||||
|
@ -6,6 +6,8 @@ package org.chromium.chrome.browser.tasks.tab_management;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
|
||||
|
||||
/** Holds dependencies for constructing a {@link TabSwitcherPane}. */
|
||||
public class TabSwitcherPaneCoordinatorFactory {
|
||||
TabSwitcherPaneCoordinatorFactory() {}
|
||||
@ -20,4 +22,13 @@ public class TabSwitcherPaneCoordinatorFactory {
|
||||
TabSwitcherPaneCoordinator create(ViewGroup parentView) {
|
||||
return new TabSwitcherPaneCoordinator(parentView);
|
||||
}
|
||||
|
||||
/** Returns the {@link TabListMode} of the produced {@link TabListCoordinator}s. */
|
||||
public @TabListMode int getTabListMode() {
|
||||
// This value will be determined at initialization time based on whether the device is
|
||||
// low-end and is not subject to change. Certain behaviors are limited to LIST vs GRID
|
||||
// mode and this information may be required even if a coordinator does not exist.
|
||||
assert false : "Not implemented.";
|
||||
return TabListMode.NUM_ENTRIES;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
@ -18,6 +19,8 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
@ -29,12 +32,16 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
import org.chromium.base.supplier.ObservableSupplier;
|
||||
import org.chromium.base.supplier.ObservableSupplierImpl;
|
||||
import org.chromium.base.supplier.OneshotSupplierImpl;
|
||||
import org.chromium.base.test.BaseRobolectricTestRunner;
|
||||
import org.chromium.chrome.browser.hub.DisplayButtonData;
|
||||
import org.chromium.chrome.browser.hub.FullButtonData;
|
||||
@ -42,7 +49,15 @@ import org.chromium.chrome.browser.hub.HubContainerView;
|
||||
import org.chromium.chrome.browser.hub.HubLayoutAnimationType;
|
||||
import org.chromium.chrome.browser.hub.LoadHint;
|
||||
import org.chromium.chrome.browser.hub.PaneId;
|
||||
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
|
||||
import org.chromium.chrome.browser.price_tracking.PriceTrackingUtilities;
|
||||
import org.chromium.chrome.browser.profiles.Profile;
|
||||
import org.chromium.chrome.browser.profiles.ProfileProvider;
|
||||
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
|
||||
import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
|
||||
import org.chromium.chrome.browser.toolbar.TabSwitcherDrawable;
|
||||
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
|
||||
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController.MenuOrKeyboardActionHandler;
|
||||
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.BackPressResult;
|
||||
|
||||
/** Unit tests for {@link TabSwitcherPane} and {@link TabSwitcherPaneBase}. */
|
||||
@ -50,13 +65,23 @@ import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.BackPr
|
||||
public class TabSwitcherPaneUnitTest {
|
||||
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock private SharedPreferences mSharedPreferences;
|
||||
@Mock private Profile mProfile;
|
||||
@Mock private ProfileProvider mProfileProvider;
|
||||
@Mock private TabSwitcherPaneCoordinatorFactory mTabSwitcherPaneCoordinatorFactory;
|
||||
@Mock private TabSwitcherPaneCoordinator mTabSwitcherPaneCoordinator;
|
||||
@Mock private TabSwitcherPaneDrawableCoordinator mTabSwitcherPaneDrawableCoordinator;
|
||||
@Mock private TabSwitcherDrawable mTabSwitcherDrawable;
|
||||
@Mock private HubContainerView mHubContainerView;
|
||||
@Mock private View.OnClickListener mNewTabButtonClickListener;
|
||||
@Mock private TabModelFilter mTabModelFilter;
|
||||
@Mock private MenuOrKeyboardActionController mMenuOrKeyboardActionController;
|
||||
|
||||
@Captor ArgumentCaptor<MenuOrKeyboardActionHandler> mMenuOrKeyboardActionHandlerCaptor;
|
||||
@Captor ArgumentCaptor<OnSharedPreferenceChangeListener> mPriceAnnotationsPrefListenerCaptor;
|
||||
|
||||
private final OneshotSupplierImpl<ProfileProvider> mProfileProviderSupplier =
|
||||
new OneshotSupplierImpl<>();
|
||||
private Context mContext;
|
||||
private ObservableSupplierImpl<Boolean> mHandleBackPressChangeSupplier =
|
||||
new ObservableSupplierImpl<>();
|
||||
@ -67,6 +92,13 @@ public class TabSwitcherPaneUnitTest {
|
||||
public void setUp() {
|
||||
mContext = ApplicationProvider.getApplicationContext();
|
||||
when(mHubContainerView.getContext()).thenReturn(mContext);
|
||||
|
||||
PriceTrackingFeatures.setPriceTrackingEnabledForTesting(true);
|
||||
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
|
||||
|
||||
when(mProfileProvider.getOriginalProfile()).thenReturn(mProfile);
|
||||
mProfileProviderSupplier.set(mProfileProvider);
|
||||
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
mTimesCreated++;
|
||||
@ -74,9 +106,9 @@ public class TabSwitcherPaneUnitTest {
|
||||
})
|
||||
.when(mTabSwitcherPaneCoordinatorFactory)
|
||||
.create(any());
|
||||
when(mTabSwitcherPaneCoordinatorFactory.getTabListMode()).thenReturn(TabListMode.GRID);
|
||||
when(mTabSwitcherPaneCoordinator.getHandleBackPressChangedSupplier())
|
||||
.thenReturn(mHandleBackPressChangeSupplier);
|
||||
|
||||
mHandleBackPressChangeSupplier.set(false);
|
||||
when(mTabSwitcherPaneDrawableCoordinator.getTabSwitcherDrawable())
|
||||
.thenReturn(mTabSwitcherDrawable);
|
||||
@ -92,15 +124,28 @@ public class TabSwitcherPaneUnitTest {
|
||||
mTabSwitcherPane =
|
||||
new TabSwitcherPane(
|
||||
mContext,
|
||||
mSharedPreferences,
|
||||
mProfileProviderSupplier,
|
||||
mTabSwitcherPaneCoordinatorFactory,
|
||||
() -> mTabModelFilter,
|
||||
mNewTabButtonClickListener,
|
||||
mMenuOrKeyboardActionController,
|
||||
mTabSwitcherPaneDrawableCoordinator);
|
||||
ShadowLooper.runUiThreadTasks();
|
||||
verify(mSharedPreferences)
|
||||
.registerOnSharedPreferenceChangeListener(
|
||||
mPriceAnnotationsPrefListenerCaptor.capture());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mTabSwitcherPane.destroy();
|
||||
verify(mTabSwitcherPaneCoordinator, times(mTimesCreated)).destroy();
|
||||
verify(mMenuOrKeyboardActionController, atLeastOnce())
|
||||
.unregisterMenuOrKeyboardActionHandler(any());
|
||||
verify(mSharedPreferences)
|
||||
.unregisterOnSharedPreferenceChangeListener(
|
||||
mPriceAnnotationsPrefListenerCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -209,6 +254,83 @@ public class TabSwitcherPaneUnitTest {
|
||||
.getPlannedAnimationType());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testPriceTracking() {
|
||||
mTabSwitcherPane.notifyLoadHint(LoadHint.HOT);
|
||||
assertNull(mTabSwitcherPane.getTabSwitcherPaneCoordinator());
|
||||
when(mTabModelFilter.isCurrentlySelectedFilter()).thenReturn(true);
|
||||
when(mTabModelFilter.isTabModelRestored()).thenReturn(true);
|
||||
|
||||
OnSharedPreferenceChangeListener listener = mPriceAnnotationsPrefListenerCaptor.getValue();
|
||||
// Check this doesn't crash if there is no coordinator.
|
||||
listener.onSharedPreferenceChanged(
|
||||
mSharedPreferences, PriceTrackingUtilities.TRACK_PRICES_ON_TABS);
|
||||
|
||||
mTabSwitcherPane.initWithNative();
|
||||
mTabSwitcherPane.createTabSwitcherPaneCoordinator();
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPane.getTabSwitcherPaneCoordinator();
|
||||
|
||||
listener.onSharedPreferenceChanged(
|
||||
mSharedPreferences, PriceTrackingUtilities.TRACK_PRICES_ON_TABS);
|
||||
verify(coordinator).resetWithTabList(mTabModelFilter);
|
||||
|
||||
when(mTabModelFilter.isTabModelRestored()).thenReturn(false);
|
||||
listener.onSharedPreferenceChanged(
|
||||
mSharedPreferences, PriceTrackingUtilities.TRACK_PRICES_ON_TABS);
|
||||
verify(coordinator).resetWithTabList(mTabModelFilter);
|
||||
when(mTabModelFilter.isTabModelRestored()).thenReturn(true);
|
||||
|
||||
when(mTabModelFilter.isCurrentlySelectedFilter()).thenReturn(false);
|
||||
listener.onSharedPreferenceChanged(
|
||||
mSharedPreferences, PriceTrackingUtilities.TRACK_PRICES_ON_TABS);
|
||||
verify(coordinator).resetWithTabList(mTabModelFilter);
|
||||
when(mTabModelFilter.isTabModelRestored()).thenReturn(true);
|
||||
|
||||
listener.onSharedPreferenceChanged(mSharedPreferences, "foo");
|
||||
verify(coordinator).resetWithTabList(mTabModelFilter);
|
||||
|
||||
mTabSwitcherPane.notifyLoadHint(LoadHint.WARM);
|
||||
listener.onSharedPreferenceChanged(
|
||||
mSharedPreferences, PriceTrackingUtilities.TRACK_PRICES_ON_TABS);
|
||||
verify(coordinator).resetWithTabList(mTabModelFilter);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testShowTabListEditor() {
|
||||
verify(mMenuOrKeyboardActionController, never()).registerMenuOrKeyboardActionHandler(any());
|
||||
mTabSwitcherPane.notifyLoadHint(LoadHint.HOT);
|
||||
verify(mMenuOrKeyboardActionController)
|
||||
.registerMenuOrKeyboardActionHandler(mMenuOrKeyboardActionHandlerCaptor.capture());
|
||||
|
||||
MenuOrKeyboardActionHandler handler = mMenuOrKeyboardActionHandlerCaptor.getValue();
|
||||
// Check this doesn't crash if there is no coordinator.
|
||||
assertFalse(
|
||||
handler.handleMenuOrKeyboardAction(
|
||||
org.chromium.chrome.tab_ui.R.id.menu_select_tabs, false));
|
||||
|
||||
mTabSwitcherPane.initWithNative();
|
||||
mTabSwitcherPane.createTabSwitcherPaneCoordinator();
|
||||
TabSwitcherPaneCoordinator coordinator = mTabSwitcherPane.getTabSwitcherPaneCoordinator();
|
||||
|
||||
assertFalse(
|
||||
handler.handleMenuOrKeyboardAction(
|
||||
org.chromium.chrome.tab_ui.R.id.new_tab_menu_id, false));
|
||||
verify(coordinator, never()).showTabListEditor();
|
||||
|
||||
assertTrue(
|
||||
handler.handleMenuOrKeyboardAction(
|
||||
org.chromium.chrome.tab_ui.R.id.menu_select_tabs, false));
|
||||
verify(coordinator).showTabListEditor();
|
||||
|
||||
mTabSwitcherPane.notifyLoadHint(LoadHint.WARM);
|
||||
verify(mMenuOrKeyboardActionController).unregisterMenuOrKeyboardActionHandler(handler);
|
||||
mTabSwitcherPane.notifyLoadHint(LoadHint.COLD);
|
||||
verify(mMenuOrKeyboardActionController, times(2))
|
||||
.unregisterMenuOrKeyboardActionHandler(handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
public void testGetDialogVisibilitySupplier() {
|
||||
|
Reference in New Issue
Block a user