Support preview on long press

Added support for previewing images and videos on long press.

Modified tests for Single Select Preview to use long click.

Added new tests for Multi Select Preview on long click.

Bug: 197084072
Test: atest
com.android.providers.media.photopicker.espresso.PreviewSingleSelectTest
Test: atest
com.android.providers.media.photopicker.espresso.PreviewMultiSelectLongPressTest
Test: atest
com.android.providers.media.photopicker.espresso.PreviewMultiSelectTest
Test: atest
android.photopicker.cts.PhotoPickerTest#testMultiSelect_longPress
Test: atest CtsPhototPickerTest
Test: atest com.android.providers.media.photopicker.espresso

Change-Id: I01570109acca4409288f3b1d21a36629a08a4084
diff --git a/res/layout/fragment_preview.xml b/res/layout/fragment_preview.xml
index 1cace16..ae26636 100644
--- a/res/layout/fragment_preview.xml
+++ b/res/layout/fragment_preview.xml
@@ -47,7 +47,7 @@
         android:layout_height="wrap_content">
 
         <Button
-            android:id="@+id/preview_select_button"
+            android:id="@+id/preview_select_check_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="start|center_vertical"
@@ -61,7 +61,7 @@
             android:textSize="@dimen/preview_deselect_text_size" />
 
         <Button
-            android:id="@+id/preview_add_button"
+            android:id="@+id/preview_add_or_select_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="end|center_vertical"
diff --git a/src/com/android/providers/media/photopicker/data/Selection.java b/src/com/android/providers/media/photopicker/data/Selection.java
index 84fb4c7..7d7883c 100644
--- a/src/com/android/providers/media/photopicker/data/Selection.java
+++ b/src/com/android/providers/media/photopicker/data/Selection.java
@@ -84,11 +84,11 @@
     }
 
     /**
-     * Delete the selected {@code item} from the selected item list {@link #mSelectedItems}.
+     * Remove the {@code item} from the selected item list {@link #mSelectedItems}.
      *
-     * @param item the item to be deleted from the selected item list
+     * @param item the item to be removed from the selected item list
      */
-    public void deleteSelectedItem(Item item) {
+    public void removeSelectedItem(Item item) {
         mSelectedItems.remove(item.getContentUri());
         mSelectedItemSize.postValue(mSelectedItems.size());
         updateSelectionAllowed();
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
index fbb7013..601650f 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
@@ -40,15 +40,18 @@
     public static final int COLUMN_COUNT = 3;
 
     private List<Item> mItemList = new ArrayList<>();
-    private ImageLoader mImageLoader;
-    private View.OnClickListener mOnClickListener;
-    private Selection mSelection;
+    private final ImageLoader mImageLoader;
+    private final View.OnClickListener mOnClickListener;
+    private final View.OnLongClickListener mOnLongClickListener;
+    private final Selection mSelection;
 
     public PhotosTabAdapter(@NonNull Selection selection, @NonNull ImageLoader imageLoader,
-            @NonNull View.OnClickListener listener) {
+            @NonNull View.OnClickListener onClickListener,
+            @NonNull View.OnLongClickListener onLongClickListener) {
         mImageLoader = imageLoader;
         mSelection = selection;
-        mOnClickListener = listener;
+        mOnClickListener = onClickListener;
+        mOnLongClickListener = onLongClickListener;
     }
 
     @NonNull
@@ -68,6 +71,7 @@
 
         if (getItemViewType(position) == ITEM_TYPE_PHOTO) {
             itemHolder.itemView.setOnClickListener(mOnClickListener);
+            itemHolder.itemView.setOnLongClickListener(mOnLongClickListener);
             itemHolder.itemView.setSelected(mSelection.isItemSelected(item));
         }
         itemHolder.bind();
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
index bf63276..51e3052 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
@@ -75,7 +75,7 @@
         super.onViewCreated(view, savedInstanceState);
 
         final PhotosTabAdapter adapter = new PhotosTabAdapter(mSelection, mImageLoader,
-                this::onItemClick);
+                this::onItemClick, this::onItemLongClick);
 
         mIsDefaultCategory = TextUtils.equals(Category.CATEGORY_DEFAULT, mCategoryType);
         if (mIsDefaultCategory) {
@@ -141,7 +141,7 @@
             final boolean isSelectedBefore = view.isSelected();
 
             if (isSelectedBefore) {
-                mSelection.deleteSelectedItem((Item) view.getTag());
+                mSelection.removeSelectedItem((Item) view.getTag());
             } else {
                 if (!mSelection.isSelectionAllowed()) {
                     final int maxCount = mSelection.getMaxSelectionLimit();
@@ -161,12 +161,27 @@
         } else {
             Item item = (Item) view.getTag();
             mSelection.setSelectedItem(item);
-            mSelection.prepareItemForPreviewOnLongPress(item);
-            // Transition to PreviewFragment.
-            PreviewFragment.show(getActivity().getSupportFragmentManager());
+            ((PhotoPickerActivity) getActivity()).setResultAndFinishSelf();
         }
     }
 
+    private boolean onItemLongClick(@NonNull View view) {
+        Item item = (Item) view.getTag();
+        if (!mSelection.canSelectMultiple()) {
+            // In single select mode, if the item is previewed, we set it as selected item. This is
+            // will assist in "Add" button click to return all selected items.
+            // For multi select, long click only previews the item, and until user selects the item,
+            // it doesn't get added to selected items. Also, there is no "Add" button in the preview
+            // layout that can return selected items.
+            mSelection.setSelectedItem(item);
+        }
+        mSelection.prepareItemForPreviewOnLongPress(item);
+        // Transition to PreviewFragment.
+        PreviewFragment.show(getActivity().getSupportFragmentManager(),
+                PreviewFragment.getArgsForPreviewOnLongPress());
+        return true;
+    }
+
     /**
      * Create the fragment with the category and add it into the FragmentManager
      *
diff --git a/src/com/android/providers/media/photopicker/ui/PreviewFragment.java b/src/com/android/providers/media/photopicker/ui/PreviewFragment.java
index e59be71..d9a16de 100644
--- a/src/com/android/providers/media/photopicker/ui/PreviewFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PreviewFragment.java
@@ -51,6 +51,19 @@
 public class PreviewFragment extends Fragment {
     private static String TAG = "PreviewFragment";
 
+    private static final String PREVIEW_TYPE = "preview_type";
+    private static final int PREVIEW_ON_LONG_PRESS = 1;
+    private static final int PREVIEW_ON_VIEW_SELECTED = 2;
+
+    private static final Bundle sPreviewOnLongPressArgs = new Bundle();
+    static {
+        sPreviewOnLongPressArgs.putInt(PREVIEW_TYPE, PREVIEW_ON_LONG_PRESS);
+    }
+    private static final Bundle sPreviewOnViewSelectedArgs = new Bundle();
+    static {
+        sPreviewOnViewSelectedArgs.putInt(PREVIEW_TYPE, PREVIEW_ON_VIEW_SELECTED);
+    }
+
     private Selection mSelection;
     private ViewPager2 mViewPager;
     private PreviewAdapter mAdapter;
@@ -70,25 +83,14 @@
         final int selectedItemsListSize = selectedItemsList.size();
 
         if (selectedItemsListSize <= 0) {
-            // This should never happen
-            Log.e(TAG, "No items to preview");
-            return;
+            // This should never happen.
+            throw new IllegalStateException("No items to preview");
         } else if (selectedItemsListSize > 1 && !mSelection.canSelectMultiple()) {
             // This should never happen
-            Log.e(TAG, "Found more than one preview items in single select mode."
-                    + " Selected items count: " + selectedItemsListSize);
-            return;
+            throw new IllegalStateException("Found more than one preview items in single select"
+                    + " mode. Selected items count: " + selectedItemsListSize);
         }
 
-        Button addButton = view.findViewById(R.id.preview_add_button);
-
-        // On clicking add button we return the picker result to calling app.
-        // This destroys PickerActivity and all fragments.
-        addButton.setOnClickListener(v -> {
-            ((PhotoPickerActivity) getActivity()).setResultAndFinishSelf();
-        });
-
-        // TODO(b/169737802): Support Videos
         // Initialize adapter to hold selected items
         ImageLoader imageLoader = new ImageLoader(getContext());
         mAdapter = new PreviewAdapter(imageLoader);
@@ -100,37 +102,94 @@
         mViewPager.setPageTransformer(new MarginPageTransformer(
                 getResources().getDimensionPixelSize(R.dimen.preview_viewpager_margin)));
 
-        Button selectButton = view.findViewById(R.id.preview_select_button);
+        setUpPreviewLayout(view, getArguments());
+    }
+
+    private void setUpPreviewLayout(@NonNull View view, @Nullable Bundle args) {
+        if (args == null) {
+            // We are willing to crash PhotoPickerActivity because this error might only happen
+            // during development.
+            throw new IllegalArgumentException("Can't determine the type of the Preview, arguments"
+                    + " is not set");
+        }
+
+        final Button addOrSelectButton = view.findViewById(R.id.preview_add_or_select_button);
+        final Button selectCheckButton = view.findViewById(R.id.preview_select_check_button);
+        final int previewType = args.getInt(PREVIEW_TYPE, -1);
+        if (previewType == PREVIEW_ON_LONG_PRESS) {
+            setUpPreviewLayoutForLongPress(addOrSelectButton, selectCheckButton);
+        } else if (previewType == PREVIEW_ON_VIEW_SELECTED) {
+            setUpPreviewLayoutForViewSelected(addOrSelectButton, selectCheckButton);
+        } else {
+            // We are willing to crash PhotoPickerActivity because this error might only happen
+            // during development.
+            throw new IllegalArgumentException("No preview type specified");
+        }
+    }
+
+    /**
+     * Adjusts the select/add button layout for preview on LongPress
+     */
+    private void setUpPreviewLayoutForLongPress(@NonNull Button addOrSelectButton,
+            @NonNull Button selectCheckButton) {
+        LayoutParams layoutParams
+                = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        addOrSelectButton.setLayoutParams(layoutParams);
+
+        // This button won't be visible in Preview on LongPress. Select/Deselect action for multi
+        // select mode is handled by addOrSelect button.
+        selectCheckButton.setVisibility(View.GONE);
+
+        // Preview on Long Press will reuse AddOrSelect button as
+        // * Add button - Button with text "Add" - for single select mode
+        // * Select button - Button with text "Select"/"Deselect" based on the selection state of
+        //                   the item - for multi select mode
+        if (!mSelection.canSelectMultiple()) {
+            // On clicking add button we return the picker result to calling app.
+            // This destroys PickerActivity and all fragments.
+            addOrSelectButton.setOnClickListener(v -> {
+                ((PhotoPickerActivity) getActivity()).setResultAndFinishSelf();
+            });
+        } else {
+            // For preview on long press, we always preview only one item.
+            // Selection#getSelectedItemsForPreview is guaranteed to return only one item. Hence,
+            // we can always use position=0 as current position.
+            updateSelectButtonText(addOrSelectButton,
+                    mSelection.isItemSelected(mAdapter.getItem(0)));
+            addOrSelectButton.setOnClickListener(
+                    v -> onClickSelect(addOrSelectButton, /* shouldUpdateButtonState */ false));
+        }
+    }
+
+    /**
+     * Adjusts the layout based on Multi select and adds appropriate onClick listeners
+     */
+    private void setUpPreviewLayoutForViewSelected(@NonNull Button addButton,
+            @NonNull Button selectButton) {
+        // On clicking add button we return the picker result to calling app.
+        // This destroys PickerActivity and all fragments.
+        addButton.setOnClickListener(v -> {
+            ((PhotoPickerActivity) getActivity()).setResultAndFinishSelf();
+        });
 
         // Update the select icon and text according to the state of selection while swiping
         // between photos
         mOnPageChangeCallBack = new OnPageChangeCallBack(selectButton);
         mViewPager.registerOnPageChangeCallback(mOnPageChangeCallBack);
 
-        // Adjust the layout based on Single/Multi select and add appropriate onClick listeners
-        if (!mSelection.canSelectMultiple()) {
-            // Adjust the select and add button layout for single select
-            LayoutParams layoutParams
-                    = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
-            addButton.setLayoutParams(layoutParams);
-            selectButton.setVisibility(View.GONE);
-        } else {
-            // Update add button text to include number of items selected.
-            mSelection.getSelectedItemCount().observe(this, selectedItemCount -> {
-                addButton.setText(generateAddButtonString(getContext(), selectedItemCount));
-            });
-            selectButton.setOnClickListener(v -> {
-                onClickSelect(selectButton);
-            });
-        }
+        // Update add button text to include number of items selected.
+        mSelection.getSelectedItemCount().observe(this, selectedItemCount -> {
+            addButton.setText(generateAddButtonString(getContext(), selectedItemCount));
+        });
+
+        selectButton.setOnClickListener(
+                v -> onClickSelect(selectButton, /* shouldUpdateButtonState */ true));
     }
 
     @Override
     public void onResume() {
         super.onResume();
 
-        // TODO(185801129): Change the layout of the toolbar or add new toolbar that can overlap
-        // with image/video preview if necessary
         ((PhotoPickerActivity) getActivity()).updateCommonLayouts(LayoutModeUtils.MODE_PREVIEW,
                 /* title */"");
 
@@ -148,17 +207,30 @@
         }
     }
 
-    private void onClickSelect(@NonNull Button selectButton) {
-        // isSelected tracks new state for select button, which is opposite of old state
-        final boolean isSelected = !selectButton.isSelected();
+    private void onClickSelect(@NonNull Button selectButton, boolean shouldUpdateButtonState) {
         final Item currentItem = mAdapter.getItem(mViewPager.getCurrentItem());
+        final boolean wasSelectedBefore = mSelection.isItemSelected(currentItem);
 
-        if (isSelected) {
-            mSelection.addSelectedItem(currentItem);
+        if (wasSelectedBefore) {
+            // If the item is previously selected, current user action is to deselect the item
+            mSelection.removeSelectedItem(currentItem);
         } else {
-            mSelection.deleteSelectedItem(currentItem);
+            // If the item is not previously selected, current user action is to select the item
+            mSelection.addSelectedItem(currentItem);
         }
-        setSelected(selectButton, isSelected);
+
+        // After the user has clicked the button, current state of the button should be opposite of
+        // the previous state.
+        // If the previous state was to "Select" the item, and user clicks "Select" button,
+        // wasSelectedBefore = false. And item will be added to selected items. Now, user can only
+        // deselect the item. Hence, isSelectedNow is opposite of previous state,
+        // i.e., isSelectedNow = true.
+        final boolean isSelectedNow = !wasSelectedBefore;
+        if (shouldUpdateButtonState) {
+            updateSelectButtonStateAndText(selectButton, isSelectedNow);
+        } else {
+            updateSelectButtonText(selectButton, isSelectedNow);
+        }
     }
 
     private class OnPageChangeCallBack extends ViewPager2.OnPageChangeCallback {
@@ -175,22 +247,29 @@
 
             // Set the appropriate select/deselect state for each item in each page based on the
             // selection list.
-            setSelected(mSelectButton, mSelection.isItemSelected(mAdapter.getItem(position)));
+            updateSelectButtonStateAndText(mSelectButton,
+                    mSelection.isItemSelected(mAdapter.getItem(position)));
         }
     }
 
-    private static void setSelected(@NonNull Button selectButton, boolean isSelected) {
+    private static void updateSelectButtonStateAndText(@NonNull Button selectButton,
+            boolean isSelected) {
         selectButton.setSelected(isSelected);
+        updateSelectButtonText(selectButton, isSelected);
+    }
+
+    private static void updateSelectButtonText(@NonNull Button selectButton, boolean isSelected) {
         selectButton.setText(isSelected ? R.string.deselect : R.string.select);
     }
 
-    public static void show(FragmentManager fm) {
+    public static void show(@NonNull FragmentManager fm, @NonNull Bundle args) {
         if (fm.isStateSaved()) {
             Log.d(TAG, "Skip show preview fragment because state saved");
             return;
         }
 
         final PreviewFragment fragment = new PreviewFragment();
+        fragment.setArguments(args);
         fm.beginTransaction()
                 .replace(R.id.fragment_container, fragment, TAG)
                 .addToBackStack(TAG)
@@ -201,12 +280,20 @@
      * Get the fragment in the FragmentManager
      * @param fm the fragment manager
      */
-    public static Fragment get(FragmentManager fm) {
+    public static Fragment get(@NonNull FragmentManager fm) {
         return fm.findFragmentByTag(TAG);
     }
 
+    public static Bundle getArgsForPreviewOnLongPress() {
+        return sPreviewOnLongPressArgs;
+    }
+
+    public static Bundle getArgsForPreviewOnViewSelected() {
+        return sPreviewOnViewSelectedArgs;
+    }
+
     // TODO: There is a same method in TabFragment. To find a way to reuse it.
-    private static String generateAddButtonString(Context context, int size) {
+    private static String generateAddButtonString(@NonNull Context context, int size) {
         final String sizeString = NumberFormat.getInstance(Locale.getDefault()).format(size);
         final String template = context.getString(R.string.picker_add_button_multi_select);
         return TextUtils.expandTemplate(template, sizeString).toString();
diff --git a/src/com/android/providers/media/photopicker/ui/TabFragment.java b/src/com/android/providers/media/photopicker/ui/TabFragment.java
index f0bd3f3..a35bb11 100644
--- a/src/com/android/providers/media/photopicker/ui/TabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/TabFragment.java
@@ -88,7 +88,8 @@
             // Transition to PreviewFragment on clicking "View Selected".
             viewSelectedButton.setOnClickListener(v -> {
                 mSelection.prepareSelectedItemsForPreviewAll();
-                PreviewFragment.show(getActivity().getSupportFragmentManager());
+                PreviewFragment.show(getActivity().getSupportFragmentManager(),
+                        PreviewFragment.getArgsForPreviewOnViewSelected());
             });
             mBottomBarSize = (int) getResources().getDimension(R.dimen.picker_bottom_bar_size);
 
diff --git a/tests/src/com/android/providers/media/photopicker/data/SelectionTest.java b/tests/src/com/android/providers/media/photopicker/data/SelectionTest.java
index 4efc96b..124c9df 100644
--- a/tests/src/com/android/providers/media/photopicker/data/SelectionTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/SelectionTest.java
@@ -85,7 +85,7 @@
         mSelection.addSelectedItem(item);
         assertThat(mSelection.getSelectedItemCount().getValue()).isEqualTo(1);
 
-        mSelection.deleteSelectedItem(item);
+        mSelection.removeSelectedItem(item);
         assertThat(mSelection.getSelectedItemCount().getValue()).isEqualTo(0);
     }
 
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectLongPressTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectLongPressTest.java
new file mode 100644
index 0000000..1e89033
--- /dev/null
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectLongPressTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.media.photopicker.espresso;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.assertItemNotSelected;
+import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.assertItemSelected;
+import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.longClickItem;
+
+import static org.hamcrest.Matchers.not;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.test.espresso.Espresso;
+import androidx.test.espresso.IdlingRegistry;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+
+import com.android.providers.media.R;
+import com.android.providers.media.photopicker.viewmodel.PickerViewModel;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class PreviewMultiSelectLongPressTest extends PhotoPickerBaseTest {
+    private static final int ICON_THUMBNAIL_ID = R.id.icon_thumbnail;
+
+    @Rule
+    public ActivityScenarioRule<PhotoPickerTestActivity> mRule
+            = new ActivityScenarioRule<>(PhotoPickerBaseTest.getMultiSelectionIntent());
+
+    @Test
+    public void testPreview_multiSelect_longPress_image() {
+        onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
+
+        // Navigate to preview
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
+
+        registerIdlingResourceAndWaitForIdle();
+
+        // Verify image is previewed
+        assertMultiSelectLongPressCommonLayoutMatches();
+        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+
+        // Navigate back to Photo grid
+        onView(withContentDescription("Navigate up")).perform(click());
+
+        onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testPreview_multiSelect_longPress_video() {
+        onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
+
+        // Navigate to preview
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 3, ICON_THUMBNAIL_ID);
+
+        registerIdlingResourceAndWaitForIdle();
+
+        // Since there is no video in the video file, we get an error.
+        onView(withText(android.R.string.ok)).perform(click());
+
+        // Verify videoView is displayed
+        assertMultiSelectLongPressCommonLayoutMatches();
+        onView(withId(R.id.preview_videoView)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testPreview_multiSelect_longPress_gif() {
+        onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
+
+        // Navigate to preview
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 2, ICON_THUMBNAIL_ID);
+
+        registerIdlingResourceAndWaitForIdle();
+
+        // Verify imageView is displayed for gif preview
+        assertMultiSelectLongPressCommonLayoutMatches();
+        onView(withId(R.id.preview_imageView)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testPreview_multiSelect_longPress_select() {
+        onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
+
+        final int position = 1;
+        // Navigate to preview
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, position, ICON_THUMBNAIL_ID);
+
+        registerIdlingResourceAndWaitForIdle();
+
+        final int selectButtonId = R.id.preview_add_or_select_button;
+        // Select the item within Preview
+        onView(withId(selectButtonId)).perform(click());
+        // Check that button text is changed to "deselect"
+        onView(withId(selectButtonId)).check(matches(withText(R.string.deselect)));
+
+        // Navigate back to PhotoGrid and check that item is selected
+        onView(withContentDescription("Navigate up")).perform(click());
+
+        final int iconCheckId = R.id.icon_check;
+        assertItemSelected(PICKER_TAB_RECYCLERVIEW_ID, position, iconCheckId);
+
+        // Navigate to Preview and check the select button text
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, position, ICON_THUMBNAIL_ID);
+
+        registerIdlingResourceAndWaitForIdle();
+
+        // Check that button text is set to "deselect" and common layout matches
+        assertMultiSelectLongPressCommonLayoutMatches(/* isSelected */ true);
+
+        // Click on "Deselect" and verify text changes to "Select"
+        onView(withId(selectButtonId)).perform(click());
+        // Check that button text is changed to "select"
+        onView(withId(selectButtonId)).check(matches(withText(R.string.select)));
+
+        // Navigate back to Photo grid and verify the item is not selected
+        onView(withContentDescription("Navigate up")).perform(click());
+
+        assertItemNotSelected(PICKER_TAB_RECYCLERVIEW_ID, position, iconCheckId);
+    }
+
+    private void registerIdlingResourceAndWaitForIdle() {
+        mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
+                new ViewPager2IdlingResource(activity.findViewById(R.id.preview_viewPager)))));
+        Espresso.onIdle();
+    }
+
+    private void assertMultiSelectLongPressCommonLayoutMatches() {
+        assertMultiSelectLongPressCommonLayoutMatches(/* isSelected */ false);
+    }
+
+    private void assertMultiSelectLongPressCommonLayoutMatches(boolean isSelected) {
+        onView(withId(R.id.preview_viewPager)).check(matches(isDisplayed()));
+        onView(withId(R.id.preview_select_check_button)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.preview_add_or_select_button)).check(matches(isDisplayed()));
+        // Verify that the text in AddOrSelect button
+        if (isSelected) {
+            onView(withId(R.id.preview_add_or_select_button)).check(
+                    matches(withText(R.string.deselect)));
+        } else {
+            onView(withId(R.id.preview_add_or_select_button)).check(
+                    matches(withText(R.string.select)));
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
index 4e8abfa..decaf6e 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
@@ -19,7 +19,6 @@
 import static androidx.test.InstrumentationRegistry.getTargetContext;
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.action.ViewActions.swipeLeft;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
@@ -103,8 +102,8 @@
 
         final String addButtonString =
                 getTargetContext().getResources().getString(R.string.add);
-        final int previewAddButtonId = R.id.preview_add_button;
-        final int previewSelectButtonId = R.id.preview_select_button;
+        final int previewAddButtonId = R.id.preview_add_or_select_button;
+        final int previewSelectButtonId = R.id.preview_select_check_button;
         final String deselectString =
                 getTargetContext().getResources().getString(R.string.deselect);
 
@@ -228,8 +227,8 @@
 
     private void assertMultiSelectPreviewCommonLayoutDisplayed() {
         onView(withId(PREVIEW_VIEW_PAGER_ID)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_add_button)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_select_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.preview_add_or_select_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.preview_select_check_button)).check(matches(isDisplayed()));
     }
 
     private Matcher<View> ViewPagerMatcher(int viewPagerId, int itemViewId) {
@@ -259,8 +258,16 @@
         Espresso.onIdle();
     }
 
-    private void swipeLeftAndWait() {
-        onView(withId(PREVIEW_VIEW_PAGER_ID)).perform(swipeLeft());
+    /**
+     * A custom swipeLeft method to avoid system gestures taking over ViewActions#swipeLeft
+     */
+    private static ViewAction customSwipeLeft() {
+        return new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER,
+                GeneralLocation.CENTER_LEFT, Press.FINGER);
+    }
+
+    private static void swipeLeftAndWait() {
+        onView(withId(PREVIEW_VIEW_PAGER_ID)).perform(customSwipeLeft());
         Espresso.onIdle();
     }
 
@@ -272,7 +279,7 @@
                 GeneralLocation.CENTER_RIGHT, Press.FINGER);
     }
 
-    private void swipeRightAndWait() {
+    private static void swipeRightAndWait() {
         // Use customSwipeRight to avoid system gestures taking over ViewActions#swipeRight
         onView(withId(PREVIEW_VIEW_PAGER_ID)).perform(customSwipeRight());
         Espresso.onIdle();
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
index 98136bf..509d1f8 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
@@ -27,7 +27,7 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withParent;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
-import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.clickItem;
+import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.longClickItem;
 
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.not;
@@ -56,7 +56,7 @@
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
-        clickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
 
         registerIdlingResourceAndWaitForIdle();
 
@@ -75,7 +75,7 @@
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
-        clickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 3, ICON_THUMBNAIL_ID);
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 3, ICON_THUMBNAIL_ID);
 
         registerIdlingResourceAndWaitForIdle();
 
@@ -92,7 +92,7 @@
         onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
 
         // Navigate to preview
-        clickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 2, ICON_THUMBNAIL_ID);
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 2, ICON_THUMBNAIL_ID);
 
         registerIdlingResourceAndWaitForIdle();
 
@@ -117,7 +117,7 @@
                 .check(matches(isDisplayed()));
 
         // Navigate to preview
-        clickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
+        longClickItem(PICKER_TAB_RECYCLERVIEW_ID, /* position */ 1, ICON_THUMBNAIL_ID);
 
         registerIdlingResourceAndWaitForIdle();
 
@@ -141,9 +141,9 @@
 
     private void assertSingleSelectCommonLayoutMatches() {
         onView(withId(R.id.preview_viewPager)).check(matches(isDisplayed()));
-        onView(withId(R.id.preview_select_button)).check(matches(not(isDisplayed())));
-        onView(withId(R.id.preview_add_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.preview_select_check_button)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.preview_add_or_select_button)).check(matches(isDisplayed()));
         // Verify that the text in Add button
-        onView(withId(R.id.preview_add_button)).check(matches(withText(R.string.add)));
+        onView(withId(R.id.preview_add_or_select_button)).check(matches(withText(R.string.add)));
     }
 }
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/RecyclerViewTestUtils.java b/tests/src/com/android/providers/media/photopicker/espresso/RecyclerViewTestUtils.java
index 183f629..d41dd24 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/RecyclerViewTestUtils.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/RecyclerViewTestUtils.java
@@ -18,6 +18,7 @@
 
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.longClick;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static androidx.test.espresso.matcher.ViewMatchers.isNotSelected;
@@ -57,4 +58,10 @@
                 .atPositionOnView(position, targetViewId))
                 .perform(click());
     }
+
+    public static void longClickItem(int recyclerViewId, int position, int targetViewId) {
+        onView(withRecyclerView(recyclerViewId)
+                .atPositionOnView(position, targetViewId))
+                .perform(longClick());
+    }
 }