Add Picker bottomsheet behavior test
Bug: 200513243
Test: atest com.android.providers.media.photopicker.espresso
Change-Id: Icd2e0c7823602bca6d13975fe626f40e2f4c7d8f
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index 4b176dc..61907fd 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.app.Activity;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Color;
@@ -76,6 +77,8 @@
private static final int TAB_CHIP_TYPE_PHOTOS = 0;
private static final int TAB_CHIP_TYPE_ALBUMS = 1;
+ private static final float BOTTOM_SHEET_PEEK_HEIGHT_PERCENTAGE = 0.60f;
+
@IntDef(prefix = { "TAB_CHIP_TYPE" }, value = {
TAB_CHIP_TYPE_PHOTOS,
TAB_CHIP_TYPE_ALBUMS
@@ -317,9 +320,7 @@
private void initStateForBottomSheet() {
if (!mSelection.canSelectMultiple() && !isOrientationLandscape()) {
- final WindowManager windowManager = getSystemService(WindowManager.class);
- final Rect displayBounds = windowManager.getCurrentWindowMetrics().getBounds();
- final int peekHeight = (int) (displayBounds.height() * 0.60);
+ final int peekHeight = getBottomSheetPeekHeight(this);
mBottomSheetBehavior.setPeekHeight(peekHeight);
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
@@ -328,6 +329,12 @@
}
}
+ private static int getBottomSheetPeekHeight(Context context) {
+ final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ final Rect displayBounds = windowManager.getCurrentWindowMetrics().getBounds();
+ return (int) (displayBounds.height() * BOTTOM_SHEET_PEEK_HEIGHT_PERCENTAGE);
+ }
+
private void restoreBottomSheetState() {
// BottomSheet is always EXPANDED for landscape
if (isOrientationLandscape()) {
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/BottomSheetIdlingResource.java b/tests/src/com/android/providers/media/photopicker/espresso/BottomSheetIdlingResource.java
new file mode 100644
index 0000000..47eec97
--- /dev/null
+++ b/tests/src/com/android/providers/media/photopicker/espresso/BottomSheetIdlingResource.java
@@ -0,0 +1,63 @@
+/*
+ * 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 android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.test.espresso.IdlingResource;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+
+public class BottomSheetIdlingResource implements IdlingResource {
+ private final BottomSheetBehavior<View> mBottomSheetBehavior;
+ private ResourceCallback mResourceCallback;
+
+ public BottomSheetIdlingResource(View bottomSheetView) {
+ mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView);
+ mBottomSheetBehavior.addBottomSheetCallback(new IdleListener());
+ }
+
+ @Override
+ public String getName() {
+ return BottomSheetIdlingResource.class.getName();
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ int state = mBottomSheetBehavior.getState();
+ return state != BottomSheetBehavior.STATE_DRAGGING
+ && state != BottomSheetBehavior.STATE_SETTLING;
+ }
+
+ @Override
+ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+ mResourceCallback = resourceCallback;
+ }
+
+ private final class IdleListener extends BottomSheetBehavior.BottomSheetCallback {
+ @Override
+ public void onStateChanged(@NonNull View bottomSheet, int newState) {
+ if (mResourceCallback != null && isIdleNow()) {
+ mResourceCallback.onTransitionToIdle();
+ }
+ }
+
+ @Override
+ public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
+ }
+}
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/BottomSheetTestUtils.java b/tests/src/com/android/providers/media/photopicker/espresso/BottomSheetTestUtils.java
new file mode 100644
index 0000000..7bd5f2b
--- /dev/null
+++ b/tests/src/com/android/providers/media/photopicker/espresso/BottomSheetTestUtils.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.providers.media.R;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+
+class BottomSheetTestUtils {
+ public static void assertBottomSheetState(Activity activity, int state) {
+ final BottomSheetBehavior<View> bottomSheetBehavior =
+ BottomSheetBehavior.from(activity.findViewById(R.id.bottom_sheet));
+ assertThat(bottomSheetBehavior.getState()).isEqualTo(state);
+ if (state == STATE_COLLAPSED) {
+ final int peekHeight =
+ getBottomSheetPeekHeight(PhotoPickerBaseTest.getIsolatedContext());
+ assertThat(bottomSheetBehavior.getPeekHeight()).isEqualTo(peekHeight);
+ }
+ }
+
+ private static int getBottomSheetPeekHeight(Context context) {
+ final WindowManager windowManager = context.getSystemService(WindowManager.class);
+ final Rect displayBounds = windowManager.getCurrentWindowMetrics().getBounds();
+ return (int) (displayBounds.height() * 0.60);
+ }
+}
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java b/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java
index 569944e..f207436 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/CustomSwipeAction.java
@@ -19,15 +19,22 @@
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import android.view.View;
+
import androidx.test.espresso.Espresso;
+import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.action.GeneralLocation;
import androidx.test.espresso.action.GeneralSwipeAction;
import androidx.test.espresso.action.Press;
import androidx.test.espresso.action.Swipe;
+import androidx.test.espresso.action.ViewActions;
+import androidx.test.espresso.matcher.ViewMatchers;
import com.android.providers.media.R;
+import org.hamcrest.Matcher;
+
public class CustomSwipeAction {
private static final int PREVIEW_VIEW_PAGER_ID = R.id.preview_viewPager;
@@ -57,4 +64,31 @@
onView(withId(PREVIEW_VIEW_PAGER_ID)).perform(customSwipeRight());
Espresso.onIdle();
}
+
+ /**
+ * A custom swipeDown method to avoid 90% visibility criteria on a view
+ */
+ public static ViewAction customSwipeDownPartialScreen() {
+ return withCustomConstraints(ViewActions.swipeDown(),
+ ViewMatchers.isDisplayingAtLeast(/* areaPercentage */ 60));
+ }
+
+ private static ViewAction withCustomConstraints(ViewAction action, Matcher<View> constraints) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return constraints;
+ }
+
+ @Override
+ public String getDescription() {
+ return action.getDescription();
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ action.perform(uiController, view);
+ }
+ };
+ }
}
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java
index cd995a8..161bc52 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotoPickerActivityTest.java
@@ -30,8 +30,12 @@
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.providers.media.photopicker.espresso.BottomSheetTestUtils.assertBottomSheetState;
+import static com.android.providers.media.photopicker.espresso.CustomSwipeAction.customSwipeDownPartialScreen;
import static com.android.providers.media.photopicker.espresso.RecyclerViewMatcher.withRecyclerView;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.Matchers.allOf;
@@ -39,6 +43,8 @@
import android.app.Activity;
+import androidx.test.espresso.IdlingRegistry;
+import androidx.test.espresso.action.ViewActions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
@@ -69,6 +75,40 @@
}
@Test
+ public void testBottomSheetState() {
+ // Register bottom sheet idling resource so that we don't read bottom sheet state when
+ // in between changing states
+ registerBottomSheetStateIdlingResource();
+
+ // Single select PhotoPicker is launched in partial screen mode
+ onView(withId(DRAG_BAR_ID)).check(matches(isDisplayed()));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_COLLAPSED);
+ });
+
+ // Swipe up and check that the PhotoPicker is in full screen mode
+ onView(withId(DRAG_BAR_ID)).perform(ViewActions.swipeUp());
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_EXPANDED);
+ });
+
+ // Swipe down and check that the PhotoPicker is in partial screen mode
+ onView(withId(DRAG_BAR_ID)).perform(ViewActions.swipeDown());
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_COLLAPSED);
+ });
+
+ // Swiping down on drag bar is not strong enough as closing the bottomsheet requires a
+ // stronger downward swipe using espresso.
+ // Simply swiping down on R.id.bottom_sheet throws an error from espresso, as the view is
+ // only 60% visible, but downward swipe is only successful on an element which is 90%
+ // visible.
+ onView(withId(R.id.bottom_sheet)).perform(customSwipeDownPartialScreen());
+ assertThat(mRule.getScenario().getResult().getResultCode()).isEqualTo(
+ Activity.RESULT_CANCELED);
+ }
+
+ @Test
public void testToolbarLayout() {
onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
@@ -116,4 +156,9 @@
.atPositionOnView(0, R.id.date_header_title))
.check(matches(withText(R.string.recent)));
}
+
+ private void registerBottomSheetStateIdlingResource() {
+ mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
+ new BottomSheetIdlingResource(activity.findViewById(R.id.bottom_sheet)))));
+ }
}
\ No newline at end of file
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java b/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java
index 741f707..f79b473 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PhotosTabTest.java
@@ -42,7 +42,6 @@
import com.android.providers.media.R;
import com.android.providers.media.photopicker.util.DateTimeUtils;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
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 e30c0eb..84306a3 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewMultiSelectTest.java
@@ -31,6 +31,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.BottomSheetTestUtils.assertBottomSheetState;
import static com.android.providers.media.photopicker.espresso.CustomSwipeAction.swipeLeftAndWait;
import static com.android.providers.media.photopicker.espresso.CustomSwipeAction.swipeRightAndWait;
import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.assertItemNotSelected;
@@ -38,16 +39,19 @@
import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.clickItem;
import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.longClickItem;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;
+import android.app.Activity;
import android.view.View;
import androidx.lifecycle.ViewModelProvider;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.IdlingRegistry;
+import androidx.test.espresso.action.ViewActions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
import androidx.viewpager2.widget.ViewPager2;
@@ -74,6 +78,11 @@
@Test
public void testPreview_multiSelect_common() {
onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
+ registerBottomSheetStateIdlingResource();
+ onView(withId(DRAG_BAR_ID)).check(matches(isDisplayed()));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_EXPANDED);
+ });
// Select two items and Navigate to preview
clickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
@@ -84,6 +93,9 @@
// No dragBar in preview
onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_EXPANDED);
+ });
assertMultiSelectPreviewCommonLayoutDisplayed();
onView(withId(PREVIEW_ADD_OR_SELECT_BUTTON_ID)).check(matches(not(isDisplayed())));
@@ -97,6 +109,15 @@
// Shows dragBar after we are back to Photos tab
onView(withId(DRAG_BAR_ID)).check(matches(isDisplayed()));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_EXPANDED);
+ });
+
+ // Swiping down on drag bar or toolbar is not closing the bottom sheet as closing the
+ // bottomsheet requires a stronger downward swipe.
+ onView(withId(R.id.bottom_sheet)).perform(ViewActions.swipeDown());
+ assertThat(mRule.getScenario().getResult().getResultCode()).isEqualTo(
+ Activity.RESULT_CANCELED);
}
@Test
@@ -402,4 +423,9 @@
new ViewPager2IdlingResource(activity.findViewById(PREVIEW_VIEW_PAGER_ID)))));
Espresso.onIdle();
}
+
+ private void registerBottomSheetStateIdlingResource() {
+ mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
+ new BottomSheetIdlingResource(activity.findViewById(R.id.bottom_sheet)))));
+ }
}
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 d812ee7..8d847ef 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/PreviewSingleSelectTest.java
@@ -32,8 +32,11 @@
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.providers.media.photopicker.espresso.BottomSheetTestUtils.assertBottomSheetState;
import static com.android.providers.media.photopicker.espresso.RecyclerViewTestUtils.longClickItem;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.Matchers.allOf;
@@ -68,6 +71,12 @@
public void testPreview_singleSelect_image() {
onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
+ registerBottomSheetStateIdlingResource();
+ onView(withId(DRAG_BAR_ID)).check(matches(isDisplayed()));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_COLLAPSED);
+ });
+
// Navigate to preview
longClickItem(PICKER_TAB_RECYCLERVIEW_ID, IMAGE_1_POSITION, ICON_THUMBNAIL_ID);
@@ -75,6 +84,9 @@
// No dragBar in preview
onView(withId(DRAG_BAR_ID)).check(matches(not(isDisplayed())));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_EXPANDED);
+ });
// Verify image is previewed
assertSingleSelectCommonLayoutMatches();
@@ -89,6 +101,9 @@
onView(withId(PICKER_TAB_RECYCLERVIEW_ID)).check(matches(isDisplayed()));
// Shows dragBar after we are back to Photos tab
onView(withId(DRAG_BAR_ID)).check(matches(isDisplayed()));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_COLLAPSED);
+ });
}
@Test
@@ -240,6 +255,11 @@
Espresso.onIdle();
}
+ private void registerBottomSheetStateIdlingResource() {
+ mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
+ new BottomSheetIdlingResource(activity.findViewById(R.id.bottom_sheet)))));
+ }
+
private void assertBackgroundColorOnToolbarAndBottomBar(Activity activity, int colorResId) {
final Toolbar toolbar = activity.findViewById(R.id.toolbar);
final Drawable toolbarDrawable = toolbar.getBackground();
diff --git a/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java b/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java
index 90c2cad..14f979c 100644
--- a/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java
+++ b/tests/src/com/android/providers/media/photopicker/espresso/WorkAppsOffProfileButtonTest.java
@@ -23,11 +23,20 @@
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.providers.media.photopicker.espresso.BottomSheetTestUtils.assertBottomSheetState;
+
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
+
+import androidx.test.espresso.IdlingRegistry;
+import androidx.test.espresso.action.ViewActions;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
import com.android.providers.media.R;
+import static org.hamcrest.Matchers.not;
+
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@@ -43,11 +52,28 @@
@Rule
public ActivityScenarioRule<PhotoPickerTestActivity> mRule =
- new ActivityScenarioRule<>(PhotoPickerBaseTest.getMultiSelectionIntent());
+ new ActivityScenarioRule<>(PhotoPickerBaseTest.getSingleSelectionIntent());
@Test
public void testProfileButton_dialog() throws Exception {
+ // Register bottom sheet idling resource so that we don't read bottom sheet state when
+ // in between changing states
+ registerBottomSheetStateIdlingResource();
+
+ // Single select PhotoPicker is launched in half sheet mode
+ onView(withId(DRAG_BAR_ID)).check(matches(isDisplayed()));
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_COLLAPSED);
+ });
+
final int profileButtonId = R.id.profile_button;
+ // Verify profile button is not displayed in partial screen
+ onView(withId(profileButtonId)).check(matches(not(isDisplayed())));
+
+ onView(withId(DRAG_BAR_ID)).perform(ViewActions.swipeUp());
+ mRule.getScenario().onActivity(activity -> {
+ assertBottomSheetState(activity, STATE_EXPANDED);
+ });
// Verify profile button is displayed
onView(withId(profileButtonId)).check(matches(isDisplayed()));
// Check the text on the button. It should be "Switch to work"
@@ -59,4 +85,9 @@
onView(withText(R.string.picker_profile_work_paused_msg)).check(matches(isDisplayed()));
onView(withText(android.R.string.ok)).check(matches(isDisplayed())).perform(click());
}
+
+ private void registerBottomSheetStateIdlingResource() {
+ mRule.getScenario().onActivity((activity -> IdlingRegistry.getInstance().register(
+ new BottomSheetIdlingResource(activity.findViewById(R.id.bottom_sheet)))));
+ }
}