Commit 39131ff6 authored by Mei Liang's avatar Mei Liang Committed by Commit Bot

[CloseTabSuggestion] Integration between all components

This CL is the final CL that integrates all components in order to serve
the CloseTabSuggestion to GTS.

component that was previously landed and stripped out by Pro-guard.

Binary-Size: Enables the feature by introducing a code path which used a
Change-Id: If0fc9e2593fc2422494490b470a0c1b4464af65b
Bug: 1004570
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1959783
Commit-Queue: Mei Liang <meiliang@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#728673}
parent b4248a17
......@@ -6,9 +6,14 @@ package org.chromium.chrome.features.start_surface;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
......@@ -55,7 +60,10 @@ import org.chromium.chrome.browser.compositor.layouts.Layout;
import org.chromium.chrome.browser.flags.FeatureUtilities;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabFeatureUtilities;
import org.chromium.chrome.browser.tasks.tab_management.TabSelectionEditorTestingRobot;
import org.chromium.chrome.browser.tasks.tab_management.TabSuggestionMessageService;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcherCoordinator;
import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
import org.chromium.chrome.tab_ui.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
......@@ -696,6 +704,75 @@ public class StartSurfaceLayoutTest {
mUrl, PageTransition.TYPED | PageTransition.FROM_ADDRESS_BAR, tab);
}
@Test
@MediumTest
@Feature("TabSuggestion")
// clang-format off
@Features.EnableFeatures(ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study")
@Features.DisableFeatures(ChromeFeatureList.TAB_TO_GTS_ANIMATION)
@CommandLineFlags.Add({BASE_PARAMS + "/close_tab_suggestions_stale_time_ms/0"})
public void testTabSuggestionMessageCard_dismiss() throws InterruptedException {
// clang-format on
prepareTabs(3, 0, null);
// TODO(meiliang): Avoid using static variable for tracking state,
// TabSuggestionMessageService.isSuggestionAvailableForTesting(). Instead, we can add a
// dummy MessageObserver to track the availability of the suggestions.
CriteriaHelper.pollInstrumentationThread(
()
-> TabSuggestionMessageService.isSuggestionAvailableForTesting()
&& mActivityTestRule.getActivity()
.getTabModelSelector()
.getCurrentModel()
.getCount()
== 3);
enterGTSWithThumbnailChecking();
// TODO(meiliang): Avoid using static variable for tracking state,
// TabSwitcherCoordinator::hasAppendedMessagesForTesting. Instead, we can query the number
// of items that the inner model of the TabSwitcher has.
CriteriaHelper.pollInstrumentationThread(
TabSwitcherCoordinator::hasAppendedMessagesForTesting);
onView(withId(R.id.tab_grid_message_item)).check(matches(isDisplayed()));
onView(allOf(withId(R.id.close_button), withParent(withId(R.id.tab_grid_message_item))))
.perform(click());
onView(withId(R.id.tab_grid_message_item)).check(doesNotExist());
}
@Test
@MediumTest
@Feature("TabSuggestion")
// clang-format off
@Features.EnableFeatures(ChromeFeatureList.CLOSE_TAB_SUGGESTIONS + "<Study")
@Features.DisableFeatures(ChromeFeatureList.TAB_TO_GTS_ANIMATION)
@CommandLineFlags.Add({BASE_PARAMS + "/close_tab_suggestions_stale_time_ms/0"})
public void testTabSuggestionMessageCard_review() throws InterruptedException {
// clang-format on
prepareTabs(3, 0, null);
CriteriaHelper.pollInstrumentationThread(
()
-> TabSuggestionMessageService.isSuggestionAvailableForTesting()
&& mActivityTestRule.getActivity()
.getTabModelSelector()
.getCurrentModel()
.getCount()
== 3);
enterGTSWithThumbnailChecking();
CriteriaHelper.pollInstrumentationThread(
TabSwitcherCoordinator::hasAppendedMessagesForTesting);
onView(withId(R.id.tab_grid_message_item)).check(matches(isDisplayed()));
onView(allOf(withId(R.id.action_button), withParent(withId(R.id.tab_grid_message_item))))
.perform(click());
TabSelectionEditorTestingRobot tabSelectionEditorTestingRobot =
new TabSelectionEditorTestingRobot();
tabSelectionEditorTestingRobot.resultRobot.verifyTabSelectionEditorIsVisible();
}
private static class TabCountAssertion implements ViewAssertion {
private int mExpectedCount;
......
......@@ -6,8 +6,6 @@ package org.chromium.chrome.browser.tasks.tab_management;
import android.content.Context;
import org.chromium.base.LifetimeAssert;
import java.util.ArrayList;
import java.util.List;
......@@ -18,7 +16,6 @@ import java.util.List;
* {@link MessageService}.
*/
public class MessageCardProviderCoordinator {
private final LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this);
private final MessageCardProviderMediator mMediator;
private final List<MessageService> mMessageServices = new ArrayList<>();
......@@ -53,8 +50,5 @@ public class MessageCardProviderCoordinator {
for (int i = 0; i < mMessageServices.size(); i++) {
mMessageServices.get(i).removeObserver(mMediator);
}
// If mLifetimeAssert is GC'ed before this is called, it will throw an exception
// with a stack trace showing the stack during LifetimeAssert.create().
LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
}
}
......@@ -56,4 +56,4 @@ class MessageCardViewBinder {
});
}
}
}
\ No newline at end of file
}
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.MODEL_TYPE;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
......@@ -36,8 +38,9 @@ class MessageCardViewProperties {
.WritableObjectPropertyKey<String> DISMISS_BUTTON_CONTENT_DESCRIPTION =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {ACTION_TEXT, DESCRIPTION_TEXT,
DESCRIPTION_TEXT_TEMPLATE, MESSAGE_TYPE, ICON_PROVIDER, UI_ACTION_PROVIDER,
UI_DISMISS_ACTION_PROVIDER, MESSAGE_SERVICE_ACTION_PROVIDER,
MESSAGE_SERVICE_DISMISS_ACTION_PROVIDER, DISMISS_BUTTON_CONTENT_DESCRIPTION};
public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {ACTION_TEXT, DESCRIPTION_TEXT, DESCRIPTION_TEXT_TEMPLATE,
MESSAGE_TYPE, ICON_PROVIDER, UI_ACTION_PROVIDER, UI_DISMISS_ACTION_PROVIDER,
MESSAGE_SERVICE_ACTION_PROVIDER, MESSAGE_SERVICE_DISMISS_ACTION_PROVIDER,
DISMISS_BUTTON_CONTENT_DESCRIPTION, MODEL_TYPE};
}
\ No newline at end of file
......@@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.flags.FeatureUtilities;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.profiles.Profile;
......@@ -225,8 +226,11 @@ public class TabListCoordinator implements Destroyable {
false));
}
// TODO(crbug.com/1004570) : Support drag and drop, and swipe to dismiss when
// CLOSE_TAB_SUGGESTIONS is enabled.
if ((mMode == TabListMode.GRID || mMode == TabListMode.LIST)
&& selectionDelegateProvider == null) {
&& selectionDelegateProvider == null
&& !ChromeFeatureList.isEnabled(ChromeFeatureList.CLOSE_TAB_SUGGESTIONS)) {
ItemTouchHelper touchHelper = new ItemTouchHelper(mMediator.getItemTouchHelperCallback(
context.getResources().getDimension(R.dimen.swipe_to_dismiss_threshold),
context.getResources().getDimension(R.dimen.tab_grid_merge_threshold),
......@@ -369,4 +373,18 @@ public class TabListCoordinator implements Destroyable {
void addSpecialListItem(int index, @UiType int uiType, PropertyModel model) {
mMediator.addSpecialItemToModel(index, uiType, model);
}
/**
* Removes a special {@link org.chromium.ui.modelutil.MVCListAdapter.ListItem} that
* has the given {@code uiType} and/or its {@link PropertyModel} has the given
* {@code itemIdentifier}.
*
* @param uiType The uiType to match.
* @param itemIdentifier The itemIdentifier to match. This can be obsoleted if the {@link
* org.chromium.ui.modelutil.MVCListAdapter.ListItem} does not need additional
* identifier.
*/
void removeSpecialListItem(@UiType int uiType, int itemIdentifier) {
mMediator.removeSpecialItemFromModel(uiType, itemIdentifier);
}
}
......@@ -4,6 +4,10 @@
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.MODEL_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.ModelType.TAB;
import android.app.Activity;
import android.content.ComponentCallbacks;
import android.content.Context;
......@@ -845,6 +849,8 @@ class TabListMediator {
assert mVisible;
int count = 0;
for (int i = 0; i < mModel.size(); i++) {
if (mModel.get(i).model.get(MODEL_TYPE) != TAB) continue;
if (mModel.get(i).model.get(TabProperties.IS_SELECTED)) count++;
mModel.get(i).model.set(TabProperties.IS_SELECTED, false);
}
......@@ -859,7 +865,10 @@ class TabListMediator {
}
if (tabs.size() != mModel.size()) return false;
for (int i = 0; i < tabs.size(); i++) {
if (tabs.get(i).getId() != mModel.get(i).model.get(TabProperties.TAB_ID)) return false;
if (mModel.get(i).model.get(MODEL_TYPE) == TAB
&& mModel.get(i).model.get(TabProperties.TAB_ID) != tabs.get(i).getId()) {
return false;
}
}
return true;
}
......@@ -923,7 +932,9 @@ class TabListMediator {
void softCleanup() {
assert !mVisible;
for (int i = 0; i < mModel.size(); i++) {
mModel.get(i).model.set(TabProperties.THUMBNAIL_FETCHER, null);
if (mModel.get(i).model.get(MODEL_TYPE) == TAB) {
mModel.get(i).model.set(TabProperties.THUMBNAIL_FETCHER, null);
}
}
}
......@@ -1130,6 +1141,7 @@ class TabListMediator {
.with(TabProperties.TABSTRIP_FAVICON_BACKGROUND_COLOR_ID,
tabstripFaviconBackgroundDrawableId)
.with(TabProperties.ACCESSIBILITY_DELEGATE, mAccessibilityDelegate)
.with(MODEL_TYPE, TAB)
.build();
if (mUiType == UiType.SELECTABLE) {
......@@ -1284,6 +1296,37 @@ class TabListMediator {
mModel.add(index, new SimpleRecyclerViewAdapter.ListItem(uiType, model));
}
/**
* Removes a special {@link @link org.chromium.ui.modelutil.MVCListAdapter.ListItem} that
* has the given {@code uiType} and/or its {@link PropertyModel} has the given
* {@code itemIdentifier} from the current {@link TabListModel}.
*
* @param uiType The uiType to match.
* @param itemIdentifier The itemIdentifier to match. This can be obsoleted if the {@link @link
* org.chromium.ui.modelutil.MVCListAdapter.ListItem} does not need additional
* identifier.
*/
void removeSpecialItemFromModel(@UiType int uiType, int itemIdentifier) {
int index = TabModel.INVALID_TAB_INDEX;
if (uiType == UiType.MESSAGE) {
index = mModel.lastIndexForMessageItemFromType(itemIdentifier);
}
if (index == TabModel.INVALID_TAB_INDEX) return;
assert validateItemAt(index, uiType, itemIdentifier);
mModel.removeAt(index);
}
private boolean validateItemAt(int index, @UiType int uiType, int itemIdentifier) {
if (uiType == UiType.MESSAGE) {
return mModel.get(index).type == uiType
&& mModel.get(index).model.get(MESSAGE_TYPE) == itemIdentifier;
}
return false;
}
@VisibleForTesting
View.AccessibilityDelegate getAccessibilityDelegateForTesting() {
return mAccessibilityDelegate;
......
......@@ -4,15 +4,26 @@
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.MODEL_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.ModelType.MESSAGE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.ModelType.OTHERS;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.ModelType.TAB;
import static org.chromium.chrome.browser.tasks.tab_management.TabProperties.TAB_ID;
import android.util.Pair;
import androidx.annotation.IntDef;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyListModel;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
......@@ -20,6 +31,23 @@ import java.util.List;
* {@link org.chromium.chrome.browser.tab.Tab}s.
*/
class TabListModel extends ModelList {
/**
* Required properties for each {@link PropertyModel} managed by this {@link ModelList}.
*/
static class TabListModelProperties {
/** Supported Model type within this ModelList. */
@IntDef({TAB, MESSAGE, OTHERS})
@Retention(RetentionPolicy.SOURCE)
public @interface ModelType {
int TAB = 0;
int MESSAGE = 1;
int OTHERS = 2;
}
public static final PropertyModel.ReadableIntPropertyKey MODEL_TYPE =
new PropertyModel.ReadableIntPropertyKey();
}
/**
* Convert the given tab ID to an index to match during partial updates.
* @param tabId The tab ID to search for.
......@@ -27,11 +55,42 @@ class TabListModel extends ModelList {
*/
public int indexFromId(int tabId) {
for (int i = 0; i < size(); i++) {
if (get(i).model.get(TAB_ID) == tabId) return i;
PropertyModel model = get(i).model;
if (model.get(MODEL_TYPE) == TAB && model.get(TAB_ID) == tabId) return i;
}
return TabModel.INVALID_TAB_INDEX;
}
/**
* Get the index that matches a message item that has the given message type.
* @param messageType The message type to match.
* @return The index within the model.
*/
public int lastIndexForMessageItemFromType(int messageType) {
for (int i = size() - 1; i >= 0; i--) {
PropertyModel model = get(i).model;
if (model.get(MODEL_TYPE) == MESSAGE && model.get(MESSAGE_TYPE) == messageType) {
return i;
}
}
return TabModel.INVALID_TAB_INDEX;
}
@Override
public void add(int position, MVCListAdapter.ListItem item) {
assert validateListItem(item);
super.add(position, item);
}
private boolean validateListItem(MVCListAdapter.ListItem item) {
try {
item.model.get(MODEL_TYPE);
} catch (IllegalArgumentException e) {
return false;
}
return true;
}
/**
* Sync the {@link TabListModel} with updated information. Update tab id of
* the item in {@code index} with the current selected {@code tab} of the group.
......@@ -80,8 +139,9 @@ class TabListModel extends ModelList {
int status = isSelected ? ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_IN
: ClosableTabGridView.AnimationStatus.SELECTED_CARD_ZOOM_OUT;
if (index < 0 || index >= size()
|| get(index).model.get(TabProperties.CARD_ANIMATION_STATUS) == status)
|| get(index).model.get(TabProperties.CARD_ANIMATION_STATUS) == status) {
return;
}
get(index).model.set(TabProperties.CARD_ANIMATION_STATUS, status);
get(index).model.set(TabProperties.ALPHA, isSelected ? 0.8f : 1f);
......@@ -99,8 +159,10 @@ class TabListModel extends ModelList {
int status = isHovered ? ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_IN
: ClosableTabGridView.AnimationStatus.HOVERED_CARD_ZOOM_OUT;
if (index < 0 || index >= size()
|| get(index).model.get(TabProperties.CARD_ANIMATION_STATUS) == status)
|| get(index).model.get(TabProperties.CARD_ANIMATION_STATUS) == status) {
return;
}
get(index).model.set(TabProperties.CARD_ANIMATION_STATUS, status);
}
}
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.MODEL_TYPE;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.view.View.AccessibilityDelegate;
......@@ -103,7 +105,8 @@ public class TabProperties {
CARD_ANIMATION_STATUS, SELECTABLE_TAB_CLICKED_LISTENER, TAB_SELECTION_DELEGATE,
IS_INCOGNITO, SELECTED_TAB_BACKGROUND_DRAWABLE_ID, TABSTRIP_FAVICON_BACKGROUND_COLOR_ID,
SELECTABLE_TAB_ACTION_BUTTON_BACKGROUND,
SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND, URL, ACCESSIBILITY_DELEGATE};
SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND, URL, ACCESSIBILITY_DELEGATE,
MODEL_TYPE};
public static final PropertyKey[] ALL_KEYS_TAB_STRIP =
new PropertyKey[] {TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON,
......
......@@ -4,6 +4,9 @@
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.MODEL_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.ModelType.OTHERS;
import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
......@@ -171,10 +174,9 @@ class TabSelectionEditorCoordinator {
void resetWithListOfTabs(@Nullable List<Tab> tabs, int preSelectedCount) {
mTabListCoordinator.resetWithListOfTabs(tabs);
if (tabs != null && preSelectedCount > 0) {
assert preSelectedCount < tabs.size();
mTabListCoordinator.addSpecialListItem(
preSelectedCount, TabProperties.UiType.DIVIDER, new PropertyModel());
if (tabs != null && preSelectedCount > 0 && preSelectedCount < tabs.size()) {
mTabListCoordinator.addSpecialListItem(preSelectedCount, TabProperties.UiType.DIVIDER,
new PropertyModel.Builder(MODEL_TYPE).with(MODEL_TYPE, OTHERS).build());
}
}
......
......@@ -4,6 +4,9 @@
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.MODEL_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.ModelType.MESSAGE;
import android.content.Context;
import android.graphics.drawable.Drawable;
......@@ -48,6 +51,7 @@ public class TabSuggestionMessageCardViewModel {
.with(MessageCardViewProperties.ACTION_TEXT, actionText)
.with(MessageCardViewProperties.DISMISS_BUTTON_CONTENT_DESCRIPTION,
dismissButtonContextDescription)
.with(MODEL_TYPE, MESSAGE)
.build();
}
......
......@@ -32,6 +32,7 @@ import java.util.Set;
*/
public class TabSuggestionMessageService extends MessageService implements TabSuggestionsObserver {
static final int CLOSE_SUGGESTION_ACTION_ENABLING_THRESHOLD = 1;
private static boolean sSuggestionAvailableForTesting;
/**
* This is the data type that this MessageService is serving to its Observer.
......@@ -209,6 +210,7 @@ public class TabSuggestionMessageService extends MessageService implements TabSu
Callback<TabSuggestionFeedback> tabSuggestionFeedback) {
if (tabSuggestions.size() == 0) return;
sSuggestionAvailableForTesting = true;
mCurrentBestTabSuggestion = tabSuggestions.get(0);
mCurrentTabSuggestionFeedback = tabSuggestionFeedback;
sendAvailabilityNotification(new TabSuggestionMessageData(
......@@ -218,6 +220,12 @@ public class TabSuggestionMessageService extends MessageService implements TabSu
@Override
public void onTabSuggestionInvalidated() {
mCurrentBestTabSuggestion = null;
sSuggestionAvailableForTesting = false;
sendInvalidNotification();
}
@VisibleForTesting
public static boolean isSuggestionAvailableForTesting() {
return sSuggestionAvailableForTesting;
}
}
......@@ -32,6 +32,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabList;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.tab_management.suggestions.TabSuggestionsOrchestrator;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.modelutil.PropertyModel;
......@@ -51,6 +52,7 @@ public class TabSwitcherCoordinator
// TODO(crbug.com/982018): Rename 'COMPONENT_NAME' so as to add different metrics for carousel
// tab switcher.
static final String COMPONENT_NAME = "GridTabSwitcher";
private static boolean sAppendedMessagesForTesting;
private final PropertyModelChangeProcessor mContainerViewChangeProcessor;
private final ActivityLifecycleDispatcher mLifecycleDispatcher;
private final MenuOrKeyboardActionController mMenuOrKeyboardActionController;
......@@ -62,6 +64,8 @@ public class TabSwitcherCoordinator
private final UndoGroupSnackbarController mUndoGroupSnackbarController;
private final TabModelSelector mTabModelSelector;
private final @TabListCoordinator.TabListMode int mMode;
private final MessageCardProviderCoordinator mMessageCardProviderCoordinator;
private TabSuggestionsOrchestrator mTabSuggestionsOrchestrator;
private final MenuOrKeyboardActionController
.MenuOrKeyboardActionHandler mTabSwitcherMenuActionHandler =
......@@ -121,6 +125,11 @@ public class TabSwitcherCoordinator
mContainerViewChangeProcessor = PropertyModelChangeProcessor.create(containerViewModel,
mTabListCoordinator.getContainerView(), TabListContainerViewBinder::bind);
mMessageCardProviderCoordinator = new MessageCardProviderCoordinator(context,
(identifier)
-> mTabListCoordinator.removeSpecialListItem(
TabProperties.UiType.MESSAGE, identifier));
if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
mTabGridDialogCoordinator = new TabGridDialogCoordinator(context, tabModelSelector,
tabContentManager, tabCreatorManager,
......@@ -151,6 +160,15 @@ public class TabSwitcherCoordinator
return (ViewGroup) LayoutInflater.from(context).inflate(
R.layout.tab_grid_message_card_item, container, false);
}, MessageCardViewBinder::bind);
mTabSuggestionsOrchestrator =
new TabSuggestionsOrchestrator(mTabModelSelector, lifecycleDispatcher);
TabSuggestionMessageService tabSuggestionMessageService =
new TabSuggestionMessageService(context, tabModelSelector,
mTabSelectionEditorCoordinator.getController());
mTabSuggestionsOrchestrator.addObserver(tabSuggestionMessageService);
mMessageCardProviderCoordinator.subscribeMessageService(
tabSuggestionMessageService);
}
assert mTabListCoordinator.getContainerView().getLayoutManager()
......@@ -179,6 +197,11 @@ public class TabSwitcherCoordinator
mLifecycleDispatcher.register(this);
}
@VisibleForTesting
public static boolean hasAppendedMessagesForTesting() {
return sAppendedMessagesForTesting;
}
// TabSwitcher implementation.
@Override
public void setOnTabSelectingListener(OnTabSelectingListener listener) {
......@@ -283,6 +306,7 @@ public class TabSwitcherCoordinator
// ResetHandler implementation.
@Override
public boolean resetWithTabList(@Nullable TabList tabList, boolean quickMode, boolean mruMode) {
sAppendedMessagesForTesting = false;
List<Tab> tabs = null;
if (tabList != null) {
tabs = new ArrayList<>();
......@@ -292,7 +316,21 @@ public class TabSwitcherCoordinator
}
mMediator.registerFirstMeaningfulPaintRecorder();
return mTabListCoordinator.resetWithListOfTabs(tabs, quickMode, mruMode);
boolean showQuickly = mTabListCoordinator.resetWithListOfTabs(tabs, quickMode, mruMode);
if (tabs != null && tabs.size() > 0) appendMessagesTo(tabs.size());
return showQuickly;
}
private void appendMessagesTo(int index) {
List<MessageCardProviderMediator.Message> messages =
mMessageCardProviderCoordinator.getMessageItems();
for (int i = 0; i < messages.size(); i++) {
mTabListCoordinator.addSpecialListItem(
index + i, TabProperties.UiType.MESSAGE, messages.get(i).model);
}
if (messages.size() > 0) sAppendedMessagesForTesting = true;
}
private View getTabGridDialogAnimationSourceView(int tabId) {
......@@ -318,6 +356,7 @@ public class TabSwitcherCoordinator
mMenuOrKeyboardActionController.unregisterMenuOrKeyboardActionHandler(
mTabSwitcherMenuActionHandler);
mTabListCoordinator.destroy();
mMessageCardProviderCoordinator.destroy();
mContainerViewChangeProcessor.destroy();
if (mTabGridDialogCoordinator != null) {
mTabGridDialogCoordinator.destroy();
......
......@@ -132,7 +132,7 @@ public class TabSelectionEditorTestingRobot {
public final TabSelectionEditorTestingRobot.Result resultRobot;
public final TabSelectionEditorTestingRobot.Action actionRobot;
TabSelectionEditorTestingRobot() {
public TabSelectionEditorTestingRobot() {
resultRobot = new TabSelectionEditorTestingRobot.Result();
actionRobot = new TabSelectionEditorTestingRobot.Action();
}
......@@ -140,15 +140,15 @@ public class TabSelectionEditorTestingRobot {
/**
* This Robot is used to perform action within the TabSelectionEditor.
*/
class Action {
TabSelectionEditorTestingRobot.Action clickItemAtAdapterPosition(int position) {
public class Action {
public TabSelectionEditorTestingRobot.Action clickItemAtAdapterPosition(int position) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.perform(actionOnItemAtPosition(position, click()));
return this;
}
TabSelectionEditorTestingRobot.Action clickToolbarActionButton() {
public TabSelectionEditorTestingRobot.Action clickToolbarActionButton() {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
......@@ -156,7 +156,7 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Action clickToolbarNavigationButton() {
public TabSelectionEditorTestingRobot.Action clickToolbarNavigationButton() {
onView(allOf(withContentDescription(org.chromium.chrome.tab_ui.R.string.close),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
......@@ -168,15 +168,15 @@ public class TabSelectionEditorTestingRobot {
/**
* This Robot is used to verify result within the TabSelectionEditor.
*/
class Result {
TabSelectionEditorTestingRobot.Result verifyTabSelectionEditorIsVisible() {
public class Result {
public TabSelectionEditorTestingRobot.Result verifyTabSelectionEditorIsVisible() {
onView(withId(org.chromium.chrome.tab_ui.R.id.selectable_list))
.inRoot(isTabSelectionEditorPopup())
.check(matches(isDisplayed()));
return this;
}
TabSelectionEditorTestingRobot.Result verifyTabSelectionEditorIsHidden() {
public TabSelectionEditorTestingRobot.Result verifyTabSelectionEditorIsHidden() {
try {
onView(withId(org.chromium.chrome.tab_ui.R.id.selectable_list))
.inRoot(isTabSelectionEditorPopup())
......@@ -189,7 +189,7 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarSelectionTextWithResourceId(
public TabSelectionEditorTestingRobot.Result verifyToolbarSelectionTextWithResourceId(
int resourceId) {
onView(withText(resourceId))
.inRoot(isTabSelectionEditorPopup())
......@@ -197,14 +197,14 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarSelectionText(String text) {
public TabSelectionEditorTestingRobot.Result verifyToolbarSelectionText(String text) {
onView(withText(text))
.inRoot(isTabSelectionEditorPopup())
.check(matches(isDisplayed()));
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonWithResourceId(
public TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonWithResourceId(
int resourceId) {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
......@@ -213,7 +213,8 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonWithText(String text) {
public TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonWithText(
String text) {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
......@@ -221,7 +222,7 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonDisabled() {
public TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonDisabled() {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
......@@ -229,7 +230,7 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonEnabled() {
public TabSelectionEditorTestingRobot.Result verifyToolbarActionButtonEnabled() {
onView(allOf(withId(org.chromium.chrome.tab_ui.R.id.action_button),
withParent(withId(org.chromium.chrome.tab_ui.R.id.action_bar))))
.inRoot(isTabSelectionEditorPopup())
......@@ -237,7 +238,7 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyHasAtLeastNItemVisible(int count) {
public TabSelectionEditorTestingRobot.Result verifyHasAtLeastNItemVisible(int count) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check((v, noMatchException) -> {
......@@ -249,14 +250,15 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyAdapterHasItemCount(int count) {
public TabSelectionEditorTestingRobot.Result verifyAdapterHasItemCount(int count) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check(matches(RecyclerViewMatcherUtils.adapterHasItemCount(count)));
return this;
}
TabSelectionEditorTestingRobot.Result verifyItemNotSelectedAtAdapterPosition(int position) {
public TabSelectionEditorTestingRobot.Result verifyItemNotSelectedAtAdapterPosition(
int position) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check(matches(
......@@ -264,7 +266,8 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyItemSelectedAtAdapterPosition(int position) {
public TabSelectionEditorTestingRobot.Result verifyItemSelectedAtAdapterPosition(
int position) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.check(matches(
......@@ -272,12 +275,13 @@ public class TabSelectionEditorTestingRobot {
return this;
}
TabSelectionEditorTestingRobot.Result verifyUndoSnackbarWithTextIsShown(String text) {
public TabSelectionEditorTestingRobot.Result verifyUndoSnackbarWithTextIsShown(
String text) {
onView(withText(text)).check(matches(isDisplayed()));
return this;
}
Result verifyDividerAlwaysStartsAtTheEdgeOfScreen() {
public Result verifyDividerAlwaysStartsAtTheEdgeOfScreen() {
onView(allOf(isDivider(),
withParent(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))))
.inRoot(isTabSelectionEditorPopup())
......@@ -291,7 +295,7 @@ public class TabSelectionEditorTestingRobot {
return this;
}
Result verifyDividerAlwaysStartsAtTheEdgeOfScreenAtPosition(int position) {
public Result verifyDividerAlwaysStartsAtTheEdgeOfScreenAtPosition(int position) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.perform(scrollToPosition(position));
......@@ -309,7 +313,7 @@ public class TabSelectionEditorTestingRobot {
return this;
}
Result verifyDividerNotClickableNotFocusable() {
public Result verifyDividerNotClickableNotFocusable() {
onView(allOf(isDivider(),
withParent(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))))
.inRoot(isTabSelectionEditorPopup())
......@@ -329,7 +333,7 @@ public class TabSelectionEditorTestingRobot {
* @param targetItemViewType The item view type to be matched.
* @return {@link Result} to do chain verification.
*/
Result verifyHasItemViewTypeAtAdapterPosition(int position, int targetItemViewType) {
public Result verifyHasItemViewTypeAtAdapterPosition(int position, int targetItemViewType) {
onView(withId(org.chromium.chrome.tab_ui.R.id.tab_list_view))
.inRoot(isTabSelectionEditorPopup())
.perform(scrollToPosition(position));
......
......@@ -121,4 +121,4 @@ public class MessageCardProviderMediatorUnitTest {
Assert.assertEquals(MessageService.MessageType.TAB_SUGGESTION,
model.get(MessageCardViewProperties.MESSAGE_TYPE));
}
}
\ No newline at end of file
}
......@@ -28,6 +28,11 @@ import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.ChromeFeatureList.TAB_GROUPS_ANDROID;
import static org.chromium.chrome.browser.ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID;
import static org.chromium.chrome.browser.tasks.tab_management.MessageCardViewProperties.MESSAGE_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.MessageService.MessageType.FOR_TESTING;
import static org.chromium.chrome.browser.tasks.tab_management.MessageService.MessageType.TAB_SUGGESTION;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.MODEL_TYPE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.TabListModelProperties.ModelType.MESSAGE;
import android.app.Activity;
import android.content.ComponentCallbacks;
......@@ -1399,7 +1404,9 @@ public class TabListMediatorUnitTest {
@Test
public void addSpecialItem() {
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, new PropertyModel());
PropertyModel model = mock(PropertyModel.class);
when(model.get(MODEL_TYPE)).thenReturn(MESSAGE);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, model);
assertTrue(mModel.size() > 0);
assertEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
......@@ -1407,7 +1414,9 @@ public class TabListMediatorUnitTest {
@Test
public void addSpecialItem_notPersistOnReset() {
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, new PropertyModel());
PropertyModel model = mock(PropertyModel.class);
when(model.get(MODEL_TYPE)).thenReturn(MESSAGE);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, model);
assertEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
......@@ -1416,11 +1425,33 @@ public class TabListMediatorUnitTest {
assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(1).type);
mMediator.addSpecialItemToModel(1, TabProperties.UiType.DIVIDER, new PropertyModel());
mMediator.addSpecialItemToModel(1, TabProperties.UiType.DIVIDER, model);
assertThat(mModel.size(), equalTo(3));
assertEquals(TabProperties.UiType.DIVIDER, mModel.get(1).type);
}
@Test(expected = AssertionError.class)
public void addSpecialItem_withoutTabListModelProperties() {
mMediator.addSpecialItemToModel(0, TabProperties.UiType.DIVIDER, new PropertyModel());
}
@Test
public void removeSpecialItem_Message() {
PropertyModel model = mock(PropertyModel.class);
int expectedMessageType = FOR_TESTING;
int wrongMessageType = TAB_SUGGESTION;
when(model.get(MODEL_TYPE)).thenReturn(MESSAGE);
when(model.get(MESSAGE_TYPE)).thenReturn(expectedMessageType);
mMediator.addSpecialItemToModel(0, TabProperties.UiType.MESSAGE, model);
assertEquals(1, mModel.size());
mMediator.removeSpecialItemFromModel(TabProperties.UiType.MESSAGE, wrongMessageType);
assertEquals(1, mModel.size());
mMediator.removeSpecialItemFromModel(TabProperties.UiType.MESSAGE, expectedMessageType);
assertEquals(0, mModel.size());
}
@Test
@Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
public void testUrlUpdated_forSingleTab_GTS_GroupNotEnabled() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment