Skip to content

Commit

Permalink
Check whether a Tab thumbnail has been viewed
Browse files Browse the repository at this point in the history
Checking whether a tab thumbnail has been viewed in the tab switcher grid. This information is then stored during hardCleanup() when the user leaves the tab switcher grid.

Bug: 1335197
Change-Id: I39a244dada7df5f71185bc634dda36da42adac90
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3698152
Commit-Queue: Victoria Williamson <victorianw@google.com>
Reviewed-by: Matthew Jones <mdjones@chromium.org>
Reviewed-by: Mei Liang <meiliang@chromium.org>
Reviewed-by: Zhiyuan Cai <zhiyuancai@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1033295}
  • Loading branch information
Victoria Williamson authored and Chromium LUCI CQ committed Aug 10, 2022
1 parent 104fd4f commit 1096684
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 2 deletions.
Expand Up @@ -402,13 +402,18 @@ void softCleanup() {
mMediator.softCleanup();
}

void hardCleanup() {
mMediator.hardCleanup();
}

void prepareTabSwitcherView() {
if (mGlobalLayoutListener != null) {
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
registerLayoutChangeListener();
mRecyclerView.prepareTabSwitcherView();
mMediator.prepareTabSwitcherView();
mMediator.registerOnScrolledListener(mRecyclerView);
}

private void registerLayoutChangeListener() {
Expand Down
Expand Up @@ -33,6 +33,8 @@
import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;

import org.chromium.base.Callback;
import org.chromium.base.Log;
Expand Down Expand Up @@ -99,8 +101,10 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Mediator for business logic for the tab grid. This class should be initialized with a list of
Expand Down Expand Up @@ -362,6 +366,7 @@ interface GridCardOnClickListenerProvider {

private static final String TAG = "TabListMediator";
private static Map<Integer, Integer> sTabClosedFromMapTabClosedFromMap = new HashMap<>();
private static Set<Integer> sViewedTabIds = new HashSet<>();

private final Context mContext;
private final TabListModel mModel;
Expand All @@ -384,6 +389,10 @@ interface GridCardOnClickListenerProvider {
private @UiType int mUiType;
private int mSearchChipIconDrawableId;
private GridLayoutManager mGridLayoutManager;
// mRecyclerView and mOnScrollListener are null, unless the the price drop IPH or badge is
// enabled.
private @Nullable RecyclerView mRecyclerView;
private @Nullable OnScrollListener mOnScrollListener;

private final TabActionListener mTabSelectedListener = new TabActionListener() {
@Override
Expand Down Expand Up @@ -1231,8 +1240,20 @@ boolean resetWithListOfTabs(
return false;
}

/**
* Add the tab id of a {@Tab} that has been viewed to the sViewedTabIds set.
* @param tabIndex The tab index of a {@Tab} the user has viewed.
*/
private void addViewedTabId(int tabIndex) {
assert !mTabModelSelector.getCurrentModel().isIncognito();
int tabId = mModel.get(tabIndex).model.get(TabProperties.TAB_ID);
assert TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId) != null;
sViewedTabIds.add(tabId);
}

void postHiding() {
mVisible = false;
unregisterOnScrolledListener();
}

private boolean isSelectedTab(PseudoTab tab, int tabModelSelectedTabId) {
Expand All @@ -1256,6 +1277,33 @@ void softCleanup() {
}
}

void hardCleanup() {
assert !mVisible;
if (PriceTrackingUtilities.isTrackPricesOnTabsEnabled()
&& (PriceTrackingFeatures.isPriceDropIphEnabled()
|| PriceTrackingFeatures.isPriceDropBadgeEnabled())) {
saveSeenPriceDrops();
}
sViewedTabIds.clear();
}

/**
* While leaving the tab switcher grid this update whether a tab's current price drop has or has
* not been seen.
*/
private void saveSeenPriceDrops() {
for (Integer tabId : sViewedTabIds) {
Tab tab = TabModelUtils.getTabById(mTabModelSelector.getModel(false), tabId);
if (tab != null && isUngroupedTab(tab.getId())) {
ShoppingPersistedTabData.from(tab, (sptd) -> {
if (sptd != null && sptd.getPriceDrop() != null) {
sptd.setIsCurrentPriceDropSeen(true);
}
});
}
}
}

private void updateTab(int index, PseudoTab pseudoTab, boolean isSelected, boolean isUpdatingId,
boolean quickMode) {
if (index < 0 || index >= mModel.size()) return;
Expand Down Expand Up @@ -1329,6 +1377,11 @@ public boolean isUngroupedTab(int tabId) {
return getRelatedTabsForId(tabId).size() == 1;
}

@VisibleForTesting
public Set<Integer> getViewedTabIdsForTesting() {
return sViewedTabIds;
}

/**
* @return The callback that hosts the logic for swipe and drag related actions.
*/
Expand Down Expand Up @@ -1379,6 +1432,40 @@ public int getSpanSize(int position) {
});
}

/**
* Adds an on scroll listener to {@link TabListRecyclerView} that determines whether a tab
* thumbnail is within view after a scroll is completed.
* @param recyclerView the {@link TabListRecyclerView} to add the listener too.
*/
void registerOnScrolledListener(RecyclerView recyclerView) {
if (PriceTrackingUtilities.isTrackPricesOnTabsEnabled()
&& (PriceTrackingFeatures.isPriceDropIphEnabled()
|| PriceTrackingFeatures.isPriceDropBadgeEnabled())) {
mRecyclerView = recyclerView;
mOnScrollListener = new OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (!mTabModelSelector.isIncognitoSelected()) {
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
if (mRecyclerView.getLayoutManager().isViewPartiallyVisible(
mRecyclerView.getChildAt(i), false, true)) {
addViewedTabId(i);
}
}
}
}
};
mRecyclerView.addOnScrollListener(mOnScrollListener);
}
}

private void unregisterOnScrolledListener() {
if (mRecyclerView != null && mOnScrollListener != null) {
mRecyclerView.removeOnScrollListener(mOnScrollListener);
mOnScrollListener = null;
}
}

/**
* Span count is computed based on screen width for tablets and orientation for phones.
* When in multi-window mode on phone, the span count is fixed to 2 to keep tab card size
Expand Down Expand Up @@ -1478,6 +1565,7 @@ public void destroy() {
if (mTemplateUrlObserver != null) {
TemplateUrlServiceFactory.get().removeObserver(mTemplateUrlObserver);
}
unregisterOnScrolledListener();
}

private void addTabInfoToModel(final PseudoTab pseudoTab, int index, boolean isSelected) {
Expand Down
Expand Up @@ -805,6 +805,11 @@ public void softCleanup() {
mTabListCoordinator.softCleanup();
}

@Override
public void hardCleanup() {
mTabListCoordinator.hardCleanup();
}

// ResetHandler implementation.
@Override
public void onDestroy() {
Expand Down
Expand Up @@ -188,6 +188,13 @@ interface ResetHandler {
* Release the thumbnail {@link Bitmap} but keep the {@link TabGridView}.
*/
void softCleanup();

/**
* Check to see if there are any not viewed price drops when the user leaves the tab
* switcher. This is done only before the coordinator is destroyed to reduce the amount of
* calls to ShoppingPersistedTabData.
*/
void hardCleanup();
}

/**
Expand Down Expand Up @@ -434,8 +441,10 @@ public void onChange() {
mContainerView = containerView;

mSoftClearTabListRunnable = mResetHandler::softCleanup;
mClearTabListRunnable =
() -> mResetHandler.resetWithTabList(null, false, mShowTabsInMruOrder);
mClearTabListRunnable = () -> {
mResetHandler.hardCleanup();
mResetHandler.resetWithTabList(null, false, mShowTabsInMruOrder);
};
mHandler = new Handler();
mTabContentManager = tabContentManager;

Expand Down
Expand Up @@ -169,6 +169,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Tests for {@link TabListMediator}.
*/
Expand Down Expand Up @@ -253,6 +254,8 @@ private static ProductPrice createProductPrice(long amountMicros, String currenc
@Mock
RecyclerView mRecyclerView;
@Mock
TabListRecyclerView mTabListRecyclerView;
@Mock
RecyclerView.Adapter mAdapter;
@Mock
TabGroupModelFilter mTabGroupModelFilter;
Expand Down Expand Up @@ -305,6 +308,8 @@ private static ProductPrice createProductPrice(long amountMicros, String currenc
ArgumentCaptor<ComponentCallbacks> mComponentCallbacksCaptor;
@Captor
ArgumentCaptor<TemplateUrlService.TemplateUrlServiceObserver> mTemplateUrlServiceObserver;
@Captor
ArgumentCaptor<RecyclerView.OnScrollListener> mOnScrollListenerCaptor;
@Mock
EndpointFetcher.Natives mEndpointFetcherJniMock;
@Mock
Expand Down Expand Up @@ -3145,6 +3150,31 @@ public void testRecordPriceAnnotationsEnabledMetrics() {
-1));
}

@Test
public void testPriceDropSeen() throws TimeoutException {
setPriceTrackingEnabledForTesting(true);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
PriceTrackingUtilities.TRACK_PRICES_ON_TABS, true);

doReturn(false).when(mTab1).isIncognito();
doReturn(false).when(mTab2).isIncognito();

List<Tab> tabs = new ArrayList<>();
tabs.add(mTabModel.getTabAt(0));
tabs.add(mTabModel.getTabAt(1));

mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs),
/*quickMode =*/false, /*mruMode =*/false);

prepareRecyclerViewForScroll();
mMediator.registerOnScrolledListener(mRecyclerView);
verify(mRecyclerView).addOnScrollListener(mOnScrollListenerCaptor.capture());
mOnScrollListenerCaptor.getValue().onScrolled(
mRecyclerView, /*dx =*/mTabModel.getCount(), /*dy =*/0);
assertEquals(2, mMediator.getViewedTabIdsForTesting().size());
}

private void setUpCloseButtonDescriptionString(boolean isGroup) {
if (isGroup) {
doAnswer(invocation -> {
Expand Down Expand Up @@ -3400,11 +3430,23 @@ private void prepareTestMaybeShowPriceWelcomeMessage() {
doReturn(mPriceDrop).when(mShoppingPersistedTabData).getPriceDrop();
}

private void prepareRecyclerViewForScroll() {
View seenView = mock(View.class);
for (int i = 0; i < mTabModel.getCount(); i++) {
when(mRecyclerView.getChildAt(i)).thenReturn(seenView);
}

doReturn(true).when(mGridLayoutManager).isViewPartiallyVisible(seenView, false, true);
doReturn(mTabModel.getCount()).when(mRecyclerView).getChildCount();
}

private static void setPriceTrackingEnabledForTesting(boolean value) {
FeatureList.TestValues testValues = new FeatureList.TestValues();
testValues.addFeatureFlagOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING, true);
testValues.addFieldTrialParamOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING,
PriceTrackingFeatures.PRICE_TRACKING_PARAM, String.valueOf(value));
testValues.addFieldTrialParamOverride(ChromeFeatureList.COMMERCE_PRICE_TRACKING,
PriceTrackingFeatures.PRICE_DROP_IPH_ENABLED_PARAM, String.valueOf(value));
FeatureList.setTestValues(testValues);
}
}

0 comments on commit 1096684

Please sign in to comment.