// 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.tasks.tab_management;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.chromium.base.GarbageCollectionTestUtils.canBeGarbageCollected;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.VectorDrawable;
import android.util.Size;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.widget.ImageViewCompat;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.MediumTest;
import com.google.protobuf.ByteString;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridge.OptimizationGuideCallback;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeFactory;
import org.chromium.chrome.browser.optimization_guide.OptimizationGuideBridgeFactoryJni;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.state.LevelDBPersistedDataStorage;
import org.chromium.chrome.browser.tab.state.LevelDBPersistedDataStorageJni;
import org.chromium.chrome.browser.tab.state.ShoppingPersistedTabData;
import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider.ResourceTabFavicon;
import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider.StaticTabFaviconType;
import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider.TabFavicon;
import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider.TabFaviconFetcher;
import org.chromium.chrome.browser.tab_ui.TabThumbnailView;
import org.chromium.chrome.browser.tab_ui.ThumbnailProvider;
import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
import org.chromium.chrome.tab_ui.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
import org.chromium.components.commerce.PriceTracking.BuyableProduct;
import org.chromium.components.commerce.PriceTracking.PriceTrackingData;
import org.chromium.components.commerce.PriceTracking.ProductPrice;
import org.chromium.components.commerce.PriceTracking.ProductPriceUpdate;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
import org.chromium.components.optimization_guide.OptimizationGuideDecision;
import org.chromium.components.optimization_guide.proto.CommonTypesProto.Any;
import org.chromium.components.optimization_guide.proto.HintsProto;
import org.chromium.components.payments.CurrencyFormatter;
import org.chromium.components.payments.CurrencyFormatterJni;
import org.chromium.components.tab_groups.TabGroupColorId;
import org.chromium.content_public.browser.BrowserContextHandle;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.test.util.BlankUiTestActivityTestCase;
import org.chromium.url.GURL;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests for the {@link androidx.recyclerview.widget.RecyclerView.ViewHolder} classes for {@link
* TabListCoordinator}.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({
ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
"enable-features=" + ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study",
"force-fieldtrials=Study/Group"
})
@Batch(Batch.UNIT_TESTS)
public class TabListViewHolderTest extends BlankUiTestActivityTestCase {
@Rule public JniMocker mMocker = new JniMocker();
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final String EXPECTED_PRICE_STRING = "$287";
private static final String EXPECTED_PREVIOUS_PRICE_STRING = "$314";
private static final ProductPriceUpdate PRODUCT_PRICE_UPDATE =
ProductPriceUpdate.newBuilder()
.setOldPrice(createProductPrice(10_000_000L, "USD"))
.setNewPrice(createProductPrice(5_000_000L, "USD"))
.build();
private static final BuyableProduct BUYABLE_PRODUCT =
BuyableProduct.newBuilder()
.setCurrentPrice(createProductPrice(5_000_000L, "USD"))
.build();
private static final PriceTrackingData PRICE_TRACKING_DATA =
PriceTrackingData.newBuilder()
.setBuyableProduct(BUYABLE_PRODUCT)
.setProductUpdate(PRODUCT_PRICE_UPDATE)
.build();
private static final Any ANY_PRICE_TRACKING_DATA =
Any.newBuilder()
.setValue(ByteString.copyFrom(PRICE_TRACKING_DATA.toByteArray()))
.build();
private static ProductPrice createProductPrice(long amountMicros, String currencyCode) {
return ProductPrice.newBuilder()
.setCurrencyCode(currencyCode)
.setAmountMicros(amountMicros)
.build();
}
private static final String USD_CURRENCY_SYMBOL = "$";
private static final String EXPECTED_PRICE = "$5.00";
private static final String EXPECTED_PREVIOUS_PRICE = "$10";
private static final String EXPECTED_CONTENT_DESCRIPTION =
"The price of this item recently dropped from $10 to $5.00";
private static final GURL TEST_GURL = new GURL("https://www.google.com");
private ViewGroup mTabGridView;
private PropertyModel mGridModel;
private PropertyModelChangeProcessor mGridMCP;
private ViewGroup mTabStripView;
private PropertyModel mStripModel;
private PropertyModelChangeProcessor mStripMCP;
private ViewGroup mSelectableTabGridView;
private PropertyModel mSelectableModel;
private PropertyModelChangeProcessor mSelectableMCP;
private ViewGroup mTabListView;
private ViewGroup mSelectableTabListView;
@Mock private Profile mProfile;
@Mock private LevelDBPersistedDataStorage.Natives mLevelDBPersistedTabDataStorage;
@Mock private UrlUtilities.Natives mUrlUtilitiesJniMock;
@Mock private CurrencyFormatter.Natives mCurrencyFormatterJniMock;
@Mock private OptimizationGuideBridgeFactory.Natives mOptimizationGuideBridgeFactoryJniMock;
@Mock private OptimizationGuideBridge mOptimizationGuideBridge;
private ThumbnailFetcher mMockThumbnailFetcher =
new ThumbnailFetcher(
new ThumbnailProvider() {
@Override
public void getTabThumbnailWithCallback(
int tabId,
Size thumbnailSize,
boolean isSelected,
Callback<Drawable> callback) {
mThumbnailFetchedCount.incrementAndGet();
if (!mShouldReturnBitmap) {
callback.onResult(null);
return;
}
Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
callback.onResult(new BitmapDrawable(bitmap));
}
},
Tab.INVALID_TAB_ID);
private AtomicInteger mThumbnailFetchedCount = new AtomicInteger();
private TabListMediator.TabActionListener mMockCloseListener =
new TabListMediator.TabActionListener() {
@Override
public void run(View view, int tabId) {
mCloseClicked.set(true);
mCloseTabId.set(tabId);
}
};
private AtomicBoolean mCloseClicked = new AtomicBoolean();
private AtomicInteger mCloseTabId = new AtomicInteger();
private TabListMediator.TabActionListener mMockSelectedListener =
new TabListMediator.TabActionListener() {
@Override
public void run(View view, int tabId) {
mSelectClicked.set(true);
mSelectTabId.set(tabId);
}
};
private AtomicBoolean mSelectClicked = new AtomicBoolean();
private AtomicInteger mSelectTabId = new AtomicInteger();
private TabListMediator.TabActionListener mMockCreateGroupButtonListener =
new TabListMediator.TabActionListener() {
@Override
public void run(View view, int tabId) {
mCreateGroupButtonClicked.set(true);
mCreateGroupTabId.set(tabId);
}
};
private AtomicBoolean mCreateGroupButtonClicked = new AtomicBoolean();
private AtomicInteger mCreateGroupTabId = new AtomicInteger();
private boolean mShouldReturnBitmap;
@Override
public void setUpTest() throws Exception {
super.setUpTest();
getActivity().setTheme(R.style.Theme_BrowserUI_DayNight);
MockitoAnnotations.initMocks(this);
ViewGroup view = new LinearLayout(getActivity());
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
ThreadUtils.runOnUiThreadBlocking(
() -> {
getActivity().setContentView(view, params);
mTabGridView =
(ViewGroup)
getActivity()
.getLayoutInflater()
.inflate(R.layout.tab_grid_card_item, null);
mTabStripView =
(ViewGroup)
getActivity()
.getLayoutInflater()
.inflate(R.layout.tab_strip_item, null);
mSelectableTabGridView =
(ViewGroup)
getActivity()
.getLayoutInflater()
.inflate(R.layout.tab_grid_card_item, null);
mSelectableTabListView =
(ViewGroup)
getActivity()
.getLayoutInflater()
.inflate(R.layout.tab_list_card_item, null);
mTabListView =
(ViewGroup)
getActivity()
.getLayoutInflater()
.inflate(R.layout.tab_list_card_item, null);
view.addView(mTabGridView);
view.addView(mTabStripView);
view.addView(mSelectableTabGridView);
view.addView(mSelectableTabListView);
view.addView(mTabListView);
});
int mSelectedTabBackgroundDrawableId = R.drawable.selected_tab_background;
ThreadUtils.runOnUiThreadBlocking(
() -> {
mGridModel =
new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
.with(TabProperties.TAB_ACTION_STATE, TabActionState.CLOSABLE)
.with(TabProperties.IS_INCOGNITO, false)
.with(TabProperties.TAB_ID, TAB1_ID)
.with(TabProperties.IS_SELECTED, false)
.with(TabProperties.TAB_CLICK_LISTENER, mMockSelectedListener)
.with(
TabProperties.TAB_ACTION_BUTTON_LISTENER,
mMockCloseListener)
.with(
TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID,
mSelectedTabBackgroundDrawableId)
.build();
mStripModel =
new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_STRIP)
.with(TabProperties.TAB_CLICK_LISTENER, mMockSelectedListener)
.with(
TabProperties.TAB_ACTION_BUTTON_LISTENER,
mMockCloseListener)
.with(
TabProperties.TABSTRIP_FAVICON_BACKGROUND_COLOR_ID,
R.color.favicon_background_color)
.build();
mSelectableModel =
new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
.with(TabProperties.TAB_ACTION_STATE, TabActionState.SELECTABLE)
.with(TabProperties.IS_SELECTED, false)
.with(
TabProperties.TAB_ACTION_BUTTON_LISTENER,
mMockSelectedListener)
.with(TabProperties.TAB_CLICK_LISTENER, mMockSelectedListener)
.with(
TabProperties.TAB_LONG_CLICK_LISTENER,
mMockSelectedListener)
.with(
TabProperties.TAB_SELECTION_DELEGATE,
new SelectionDelegate<>())
.with(
TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID,
mSelectedTabBackgroundDrawableId)
.build();
mGridMCP =
PropertyModelChangeProcessor.create(
mGridModel, mTabGridView, TabGridViewBinder::bindTab);
mStripMCP =
PropertyModelChangeProcessor.create(
mStripModel, mTabStripView, TabStripViewBinder::bind);
mSelectableMCP =
PropertyModelChangeProcessor.create(
mSelectableModel,
mSelectableTabGridView,
TabGridViewBinder::bindTab);
PropertyModelChangeProcessor.create(
mSelectableModel, mSelectableTabListView, TabListViewBinder::bindTab);
PropertyModelChangeProcessor.create(
mGridModel, mTabListView, TabListViewBinder::bindTab);
});
mMocker.mock(LevelDBPersistedDataStorageJni.TEST_HOOKS, mLevelDBPersistedTabDataStorage);
doNothing()
.when(mLevelDBPersistedTabDataStorage)
.init(any(LevelDBPersistedDataStorage.class), any(BrowserContextHandle.class));
doReturn(false).when(mProfile).isOffTheRecord();
LevelDBPersistedDataStorage.setSkipNativeAssertionsForTesting(true);
ProfileManager.setLastUsedProfileForTesting(mProfile);
PriceTrackingFeatures.setPriceTrackingEnabledForTesting(false);
mMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
mMocker.mock(CurrencyFormatterJni.TEST_HOOKS, mCurrencyFormatterJniMock);
doReturn(1L)
.when(mCurrencyFormatterJniMock)
.initCurrencyFormatterAndroid(
any(CurrencyFormatter.class), anyString(), anyString());
doNothing().when(mCurrencyFormatterJniMock).setMaxFractionalDigits(anyLong(), anyInt());
mMocker.mock(
OptimizationGuideBridgeFactoryJni.TEST_HOOKS,
mOptimizationGuideBridgeFactoryJniMock);
doReturn(mOptimizationGuideBridge)
.when(mOptimizationGuideBridgeFactoryJniMock)
.getForProfile(mProfile);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
}
private void testGridSelected(ViewGroup holder, PropertyModel model) {
model.set(TabProperties.IS_SELECTED, true);
Assert.assertTrue(TabUiTestHelper.isTabViewSelected(holder));
model.set(TabProperties.IS_SELECTED, false);
Assert.assertFalse(TabUiTestHelper.isTabViewSelected(holder));
}
private void tabListSelected(ViewGroup holder, PropertyModel model) {
model.set(TabProperties.IS_SELECTED, true);
Assert.assertNotNull(holder.getForeground());
model.set(TabProperties.IS_SELECTED, false);
Assert.assertNull(holder.getForeground());
}
@Test
@MediumTest
@UiThreadTest
public void testSelected() {
testGridSelected(mTabGridView, mGridModel);
mStripModel.set(TabProperties.IS_SELECTED, true);
Assert.assertNotNull(((FrameLayout) mTabStripView).getForeground());
mStripModel.set(TabProperties.IS_SELECTED, false);
Assert.assertNull(((FrameLayout) mTabStripView).getForeground());
testGridSelected(mSelectableTabGridView, mSelectableModel);
mSelectableModel.set(TabProperties.IS_SELECTED, true);
ImageView actionButton = mSelectableTabGridView.findViewById(R.id.action_button);
Assert.assertEquals(1, actionButton.getBackground().getLevel());
Assert.assertNotNull(actionButton.getDrawable());
Assert.assertEquals(255, actionButton.getDrawable().getAlpha());
mSelectableModel.set(TabProperties.IS_SELECTED, false);
Assert.assertEquals(0, actionButton.getBackground().getLevel());
Assert.assertEquals(0, actionButton.getDrawable().getAlpha());
tabListSelected(mSelectableTabListView, mSelectableModel);
mSelectableModel.set(TabProperties.IS_SELECTED, true);
ImageView endButton = mSelectableTabListView.findViewById(R.id.end_button);
Assert.assertEquals(1, endButton.getBackground().getLevel());
Assert.assertNotNull(endButton.getDrawable());
Assert.assertEquals(255, actionButton.getDrawable().getAlpha());
mSelectableModel.set(TabProperties.IS_SELECTED, false);
Assert.assertEquals(0, endButton.getBackground().getLevel());
Assert.assertEquals(0, endButton.getDrawable().getAlpha());
}
@Test
@MediumTest
public void testAnimationRestored() {
View backgroundView = mTabGridView.findViewById(R.id.background_view);
ThreadUtils.runOnUiThreadBlocking(
() -> {
mGridModel.set(TabProperties.IS_SELECTED, true);
mGridModel.set(
TabProperties.CARD_ANIMATION_STATUS,
TabGridView.AnimationStatus.CARD_RESTORE);
});
CriteriaHelper.pollUiThread(() -> !((TabGridView) mTabGridView).getIsAnimatingForTesting());
Assert.assertEquals(View.GONE, backgroundView.getVisibility());
Assert.assertTrue(TabUiTestHelper.isTabViewSelected(mTabGridView));
ThreadUtils.runOnUiThreadBlocking(
() -> {
mGridModel.set(TabProperties.IS_SELECTED, false);
mGridModel.set(
TabProperties.CARD_ANIMATION_STATUS,
TabGridView.AnimationStatus.CARD_RESTORE);
});
CriteriaHelper.pollUiThread(() -> !((TabGridView) mTabGridView).getIsAnimatingForTesting());
Assert.assertEquals(View.GONE, backgroundView.getVisibility());
Assert.assertFalse(TabUiTestHelper.isTabViewSelected(mTabGridView));
}
@Test
@MediumTest
@UiThreadTest
public void testTitle() {
final String title = "Surf the cool webz";
mGridModel.set(TabProperties.TITLE, title);
TextView textView = mTabGridView.findViewById(R.id.tab_title);
Assert.assertEquals(textView.getText(), title);
mSelectableModel.set(TabProperties.TITLE, title);
textView = mSelectableTabGridView.findViewById(R.id.tab_title);
Assert.assertEquals(textView.getText(), title);
textView = mSelectableTabListView.findViewById(R.id.title);
Assert.assertEquals(textView.getText(), title);
}
@Test
@MediumTest
@UiThreadTest
public void testThumbnail() {
mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
TabThumbnailView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
assertNotNull("Thumbnail should have a background drawable.", thumbnail.getBackground());
assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceholder());
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
assertThat(thumbnail.getDrawable(), instanceOf(VectorDrawable.class));
assertTrue(
"Thumbnail placeholder should be used when the thumbnail fetcher is null.",
thumbnail.isPlaceholder());
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(
"Thumbnail should be set.",
thumbnail.getDrawable(),
instanceOf(BitmapDrawable.class));
assertNull("Thumbnail should not have a background drawable.", thumbnail.getBackground());
assertFalse("Thumbnail should not be set to a place holder.", thumbnail.isPlaceholder());
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@Test
@MediumTest
@UiThreadTest
public void testThumbnailGridCardSize() {
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
TabThumbnailView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
assertNotNull("Thumbnail should have a background drawable.", thumbnail.getBackground());
assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceholder());
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertNotNull("Thumbnail should have a background drawable.", thumbnail.getBackground());
assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceholder());
mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(
"Thumbnail should be set.",
thumbnail.getDrawable(),
instanceOf(BitmapDrawable.class));
assertNull("Thumbnail should not have a background drawable.", thumbnail.getBackground());
assertFalse("Thumbnail should not be set to a place holder.", thumbnail.isPlaceholder());
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testThumbnailGCAfterNullBitmap() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mShouldReturnBitmap = false;
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
Assert.assertTrue(canBeGarbageCollected(ref));
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testThumbnailGCAfterNewBitmap() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
Assert.assertTrue(canBeGarbageCollected(ref));
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testResetThumbnailGC() {
ImageView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
Assert.assertTrue(canBeGarbageCollected(ref));
}
@SuppressWarnings("UnusedAssignment") // Intentionally set to null for garbage collection.
@Test
@MediumTest
@UiThreadTest
public void testHiddenGC() {
TabThumbnailView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Bitmap bitmap = ((BitmapDrawable) thumbnail.getDrawable()).getBitmap();
WeakReference<Bitmap> ref = new WeakReference<>(bitmap);
bitmap = null;
Assert.assertFalse(canBeGarbageCollected(ref));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
Assert.assertTrue(canBeGarbageCollected(ref));
assertThat(thumbnail.getDrawable(), instanceOf(VectorDrawable.class));
assertTrue(
"Thumbnail placeholder should be used when the thumbnail fetcher is null.",
thumbnail.isPlaceholder());
Assert.assertEquals(1, mThumbnailFetchedCount.get());
}
@Test
@MediumTest
@UiThreadTest
public void testHiddenThenShow() {
TabThumbnailView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
mShouldReturnBitmap = true;
mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Assert.assertEquals(1, mThumbnailFetchedCount.get());
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
assertThat(thumbnail.getDrawable(), instanceOf(VectorDrawable.class));
assertTrue(
"Thumbnail placeholder should be used when the thumbnail fetcher is null.",
thumbnail.isPlaceholder());
Assert.assertEquals(1, mThumbnailFetchedCount.get());
mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailFetcher);
assertThat(thumbnail.getDrawable(), instanceOf(BitmapDrawable.class));
Assert.assertEquals(2, mThumbnailFetchedCount.get());
}
@Test
@MediumTest
@UiThreadTest
public void testCloseButtonDescription() {
ImageView listActionButton = mTabListView.findViewById(R.id.end_button);
ImageView gridActionButton = mTabGridView.findViewById(R.id.action_button);
Assert.assertNull(listActionButton.getContentDescription());
Assert.assertNull(gridActionButton.getContentDescription());
String closeTabDescription = "Close tab";
mGridModel.set(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING, closeTabDescription);
Assert.assertEquals(closeTabDescription, listActionButton.getContentDescription());
Assert.assertEquals(closeTabDescription, gridActionButton.getContentDescription());
}
@Test
@MediumTest
@UiThreadTest
public void testCloseButtonColor() {
ImageView listActionButton = mTabListView.findViewById(R.id.end_button);
ImageView gridActionButton = mTabGridView.findViewById(R.id.action_button);
// This does not test all permutations as IS_INCOGNITO is a readable property key.
boolean isIncognito = mGridModel.get(TabProperties.IS_INCOGNITO);
boolean isSelected = false;
mGridModel.set(TabProperties.IS_SELECTED, isSelected);
ColorStateList unselectedColorStateList =
TabUiThemeProvider.getActionButtonTintList(getActivity(), isIncognito, isSelected);
Assert.assertEquals(
unselectedColorStateList, ImageViewCompat.getImageTintList(gridActionButton));
Assert.assertEquals(
unselectedColorStateList, ImageViewCompat.getImageTintList(listActionButton));
isSelected = true;
mGridModel.set(TabProperties.IS_SELECTED, isSelected);
ColorStateList selectedColorStateList =
TabUiThemeProvider.getActionButtonTintList(getActivity(), isIncognito, isSelected);
// The listActionButton does not highlight so use unselected always.
Assert.assertEquals(
selectedColorStateList, ImageViewCompat.getImageTintList(gridActionButton));
Assert.assertEquals(
unselectedColorStateList, ImageViewCompat.getImageTintList(listActionButton));
}
@Test
@MediumTest
@UiThreadTest
public void testClickToClose() {
ImageView gridActionButton = mTabGridView.findViewById(R.id.action_button);
ImageView listActionButton = mTabListView.findViewById(R.id.end_button);
ImageButton button = mTabStripView.findViewById(R.id.tab_strip_item_button);
Assert.assertFalse(mCloseClicked.get());
gridActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
int firstCloseId = mCloseTabId.get();
Assert.assertEquals(TAB1_ID, firstCloseId);
mCloseClicked.set(false);
mCloseTabId.set(Tab.INVALID_TAB_ID);
Assert.assertFalse(mCloseClicked.get());
listActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
firstCloseId = mCloseTabId.get();
Assert.assertEquals(TAB1_ID, firstCloseId);
mCloseClicked.set(false);
mGridModel.set(TabProperties.TAB_ID, TAB2_ID);
gridActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
mCloseClicked.set(false);
int secondClosed = mCloseTabId.get();
mCloseClicked.set(false);
mCloseTabId.set(Tab.INVALID_TAB_ID);
Assert.assertFalse(mCloseClicked.get());
listActionButton.performClick();
Assert.assertTrue(mCloseClicked.get());
secondClosed = mCloseTabId.get();
// When TAB_ID in PropertyModel is updated, binder should close tab with updated tab ID.
Assert.assertEquals(TAB2_ID, secondClosed);
Assert.assertNotEquals(firstCloseId, secondClosed);
mCloseClicked.set(false);
mGridModel.set(TabProperties.TAB_ACTION_BUTTON_LISTENER, null);
gridActionButton.performClick();
Assert.assertFalse(mCloseClicked.get());
listActionButton.performClick();
Assert.assertFalse(mCloseClicked.get());
mStripModel.set(TabProperties.IS_SELECTED, true);
button.performClick();
Assert.assertTrue(mCloseClicked.get());
mCloseClicked.set(false);
mStripModel.set(TabProperties.IS_SELECTED, false);
button.performClick();
Assert.assertFalse(mCloseClicked.get());
}
@Test
@MediumTest
@UiThreadTest
@EnableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringPriceDrop_TabCardLabel() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
@DisableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringPriceDrop() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
@EnableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringNullPriceDrop_TabCardLabel() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setNullPriceDrop();
testPriceString(
tab, fetcher, View.GONE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
@DisableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringNullPriceDrop() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setNullPriceDrop();
testPriceString(
tab, fetcher, View.GONE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
@EnableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringPriceDropThenNull_TabCardLabel() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
fetcher.setNullPriceDrop();
testPriceString(
tab, fetcher, View.GONE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
@DisableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringPriceDropThenNull() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
fetcher.setNullPriceDrop();
testPriceString(
tab, fetcher, View.GONE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
}
@Test
@MediumTest
@UiThreadTest
@EnableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringTurnFeatureOff_TabCardLabel() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
TabCardLabelView tabCardLabelView = mTabGridView.findViewById(R.id.tab_card_label);
Assert.assertEquals(View.GONE, tabCardLabelView.getVisibility());
}
@Test
@MediumTest
@UiThreadTest
@DisableFeatures(ChromeFeatureList.DATA_SHARING)
public void testPriceStringTurnFeatureOff() {
Tab tab = MockTab.createAndInitialize(1, mProfile);
MockShoppingPersistedTabDataFetcher fetcher = new MockShoppingPersistedTabDataFetcher(tab);
fetcher.setPriceStrings(EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
testPriceString(
tab, fetcher, View.VISIBLE, EXPECTED_PRICE_STRING, EXPECTED_PREVIOUS_PRICE_STRING);
mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
PriceCardView priceCardView = mTabGridView.findViewById(R.id.price_info_box_outer);
Assert.assertEquals(View.GONE, priceCardView.getVisibility());
}
@Test
@MediumTest
@UiThreadTest
public void testActionButtonImportantForAccessibility() {
ImageView closableListActionButton = mTabListView.findViewById(R.id.end_button);
ImageView closableGridActionButton = mTabGridView.findViewById(R.id.action_button);
Assert.assertEquals(
IMPORTANT_FOR_ACCESSIBILITY_YES,
closableListActionButton.getImportantForAccessibility());
Assert.assertEquals(
IMPORTANT_FOR_ACCESSIBILITY_YES,
closableGridActionButton.getImportantForAccessibility());
ImageView selectableListActionButton = mSelectableTabListView.findViewById(R.id.end_button);
ImageView selectableGridActionButton =
mSelectableTabGridView.findViewById(R.id.action_button);
Assert.assertEquals(
IMPORTANT_FOR_ACCESSIBILITY_NO,
selectableListActionButton.getImportantForAccessibility());
Assert.assertEquals(
IMPORTANT_FOR_ACCESSIBILITY_NO,
selectableGridActionButton.getImportantForAccessibility());
}
private void testPriceString(
Tab tab,
MockShoppingPersistedTabDataFetcher fetcher,
int expectedVisibility,
String expectedCurrentPrice,
String expectedPreviousPrice) {
PriceTrackingFeatures.setPriceTrackingEnabledForTesting(true);
testGridSelected(mTabGridView, mGridModel);
mGridModel.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, fetcher);
if (ChromeFeatureList.isEnabled(ChromeFeatureList.DATA_SHARING)) {
TabCardLabelView tabCardLabelView = mTabGridView.findViewById(R.id.tab_card_label);
if (tabCardLabelView == null) {
ViewStub stub = mTabGridView.findViewById(R.id.tab_card_label_stub);
Assert.assertNotNull(stub);
Assert.assertEquals(View.GONE, expectedVisibility);
return;
}
Assert.assertEquals(expectedVisibility, tabCardLabelView.getVisibility());
if (expectedVisibility == View.VISIBLE) {
TextView labelText = tabCardLabelView.findViewById(R.id.tab_label_text);
String text = labelText.getText().toString();
Assert.assertTrue(text.contains(expectedCurrentPrice));
Assert.assertTrue(text.contains(expectedPreviousPrice));
}
} else {
PriceCardView priceCardView = mTabGridView.findViewById(R.id.price_info_box_outer);
Assert.assertEquals(expectedVisibility, priceCardView.getVisibility());
if (expectedVisibility == View.VISIBLE) {
TextView currentPrice = mTabGridView.findViewById(R.id.current_price);
TextView previousPrice = mTabGridView.findViewById(R.id.previous_price);
Assert.assertEquals(expectedCurrentPrice, currentPrice.getText());
Assert.assertEquals(expectedPreviousPrice, previousPrice.getText());
}
}
}
static class MockShoppingPersistedTabData extends ShoppingPersistedTabData {
private PriceDrop mPriceDrop;
MockShoppingPersistedTabData(Tab tab) {
super(tab);
}
public void setPriceStrings(String priceString, String previousPriceString) {
mPriceDrop = new PriceDrop(priceString, previousPriceString);
}
@Override
public PriceDrop getPriceDrop() {
return mPriceDrop;
}
}
/** Mock {@link TabListMediator.ShoppingPersistedTabDataFetcher} for testing purposes */
static class MockShoppingPersistedTabDataFetcher
extends TabListMediator.ShoppingPersistedTabDataFetcher {
private ShoppingPersistedTabData mShoppingPersistedTabData;
MockShoppingPersistedTabDataFetcher(Tab tab) {
super(tab, null);
}
public void setPriceStrings(String priceString, String previousPriceString) {
mShoppingPersistedTabData = new MockShoppingPersistedTabData(mTab);
((MockShoppingPersistedTabData) mShoppingPersistedTabData)
.setPriceStrings(priceString, previousPriceString);
}
public void setNullPriceDrop() {
mShoppingPersistedTabData = new MockShoppingPersistedTabData(mTab);
}
@Override
public void fetch(Callback<ShoppingPersistedTabData> callback) {
callback.onResult(mShoppingPersistedTabData);
}
}
@Test
@MediumTest
@UiThreadTest
public void testFaviconFetcherAllViewsAndModels() {
final TabFavicon tabFavicon =
new ResourceTabFavicon(newDrawable(), StaticTabFaviconType.ROUNDED_GLOBE);
TabFaviconFetcher fetcher =
new TabFaviconFetcher() {
@Override
public void fetch(Callback<TabFavicon> callback) {
callback.onResult(tabFavicon);
}
};
testFaviconFetcher(
mGridModel, mTabGridView.findViewById(R.id.tab_favicon), fetcher, tabFavicon);
testFaviconFetcher(
mGridModel, mTabListView.findViewById(R.id.start_icon), fetcher, tabFavicon);
testFaviconFetcher(
mSelectableModel,
mSelectableTabGridView.findViewById(R.id.tab_favicon),
fetcher,
tabFavicon);
testFaviconFetcher(
mSelectableModel,
mSelectableTabListView.findViewById(R.id.start_icon),
fetcher,
tabFavicon);
testFaviconFetcher(
mStripModel,
mTabStripView.findViewById(R.id.tab_strip_item_button),
fetcher,
tabFavicon);
}
@Test
@MediumTest
@UiThreadTest
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void testColorIcon_validNewColor() {
final int colorId = TabGroupColorId.BLUE;
final int colorLayer = 1;
ImageView colorIconView = mTabListView.findViewById(R.id.icon);
assertNull(colorIconView.getBackground());
mGridModel.set(TabProperties.TAB_GROUP_COLOR_ID, colorId);
assertEquals(colorIconView.getVisibility(), View.VISIBLE);
assertThat(colorIconView.getBackground(), instanceOf(LayerDrawable.class));
LayerDrawable layerDrawable = (LayerDrawable) colorIconView.getBackground();
assertEquals(2, layerDrawable.getNumberOfLayers());
// Check outer color layer
assertThat(layerDrawable.getDrawable(colorLayer), instanceOf(GradientDrawable.class));
GradientDrawable drawable = (GradientDrawable) layerDrawable.getDrawable(colorLayer);
assertEquals(GradientDrawable.OVAL, drawable.getShape());
assertEquals(
ColorStateList.valueOf(
ColorPickerUtils.getTabGroupColorPickerItemColor(
getActivity(), colorId, false)),
drawable.getColor());
}
@Test
@MediumTest
@UiThreadTest
@EnableFeatures(ChromeFeatureList.TAB_GROUP_PARITY_ANDROID)
public void testColorIcon_validExistingColor() {
final int colorId1 = TabGroupColorId.BLUE;
final int colorId2 = TabGroupColorId.RED;
final int colorLayer = 1;
mGridModel.set(TabProperties.TAB_GROUP_COLOR_ID, colorId1);
ImageView colorIconView = mTabListView.findViewById(R.id.icon);
assertEquals(colorIconView.getVisibility(), View.VISIBLE);
assertNotNull(colorIconView.getBackground());
mGridModel.set(TabProperties.TAB_GROUP_COLOR_ID, colorId2);
assertThat(colorIconView.getBackground(), instanceOf(LayerDrawable.class));
LayerDrawable layerDrawable = (LayerDrawable) colorIconView.getBackground();
assertEquals(2, layerDrawable.getNumberOfLayers());
// Check outer color layer
assertThat(layerDrawable.getDrawable(colorLayer), instanceOf(GradientDrawable.class));
GradientDrawable drawable = (GradientDrawable) layerDrawable.getDrawable(colorLayer);
assertEquals(GradientDrawable.OVAL, drawable.getShape());
assertEquals(
ColorStateList.valueOf(
ColorPickerUtils.getTabGroupColorPickerItemColor(
getActivity(), colorId2, false)),
drawable.getColor());
}
@Test
@MediumTest
@UiThreadTest
@EnableFeatures(ChromeFeatureList.DATA_SHARING)
public void testTabStripNotificationBubble_toggleHasNotification() {
ImageView notificationView = mTabStripView.findViewById(R.id.tab_strip_notification_bubble);
assertEquals(notificationView.getVisibility(), View.GONE);
mStripModel.set(TabProperties.HAS_NOTIFICATION_BUBBLE, true);
assertEquals(notificationView.getVisibility(), View.VISIBLE);
}
private void testFaviconFetcher(
PropertyModel model,
ImageView faviconView,
TabFaviconFetcher fetcher,
TabFavicon expectedFavicon) {
model.set(TabProperties.IS_SELECTED, true);
model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(faviconView.getDrawable());
model.set(TabProperties.FAVICON_FETCHER, fetcher);
assertNotNull(faviconView.getDrawable());
assertEquals(faviconView.getDrawable(), expectedFavicon.getSelectedDrawable());
model.set(TabProperties.FAVICON_FETCHER, null);
assertNull(faviconView.getDrawable());
}
private Drawable newDrawable() {
Bitmap image = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
Resources resources = ContextUtils.getApplicationContext().getResources();
return new BitmapDrawable(resources, image);
}
private void mockCurrencyFormatter() {
doAnswer(
new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) {
StringBuilder sb = new StringBuilder();
sb.append(USD_CURRENCY_SYMBOL);
sb.append(invocation.getArguments()[2]);
return sb.toString();
}
})
.when(mCurrencyFormatterJniMock)
.format(anyLong(), any(CurrencyFormatter.class), anyString());
}
private void mockUrlUtilities() {
doAnswer(
new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) {
return (String) invocation.getArguments()[0];
}
})
.when(mUrlUtilitiesJniMock)
.escapeQueryParamValue(anyString(), anyBoolean());
}
private void mockOptimizationGuideResponse(
@OptimizationGuideDecision int decision, Any metadata) {
doAnswer(
new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
OptimizationGuideCallback callback =
(OptimizationGuideCallback) invocation.getArguments()[2];
callback.onOptimizationGuideDecision(decision, metadata);
return null;
}
})
.when(mOptimizationGuideBridge)
.canApplyOptimization(
any(GURL.class),
any(HintsProto.OptimizationType.class),
any(OptimizationGuideCallback.class));
}
@Override
public void tearDownTest() throws Exception {
ThreadUtils.runOnUiThreadBlocking(
() -> {
mStripMCP.destroy();
mGridMCP.destroy();
mSelectableMCP.destroy();
});
super.tearDownTest();
}
}