148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki/*
248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * Copyright (C) 2016 The Android Open Source Project
348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki *
448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * Licensed under the Apache License, Version 2.0 (the "License");
548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * you may not use this file except in compliance with the License.
648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * You may obtain a copy of the License at
748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki *
848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki *      http://www.apache.org/licenses/LICENSE-2.0
948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki *
1048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * Unless required by applicable law or agreed to in writing, software
1148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * distributed under the License is distributed on an "AS IS" BASIS,
1248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * See the License for the specific language governing permissions and
1448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki * limitations under the License.
1548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki */
1648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
1748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakipackage android.support.design.widget;
1848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
196bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
206bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.os.SystemClock;
216bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.annotation.LayoutRes;
22dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Arakiimport android.support.annotation.NonNull;
2348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.design.test.R;
24ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikovimport android.support.test.InstrumentationRegistry;
2548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.Espresso;
2648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.IdlingResource;
276bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.NoMatchingViewException;
286bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.UiController;
296bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.ViewAction;
306bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.ViewAssertion;
31e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Arakiimport android.support.test.espresso.action.CoordinatesProvider;
3248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.GeneralLocation;
3348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.GeneralSwipeAction;
346bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.action.MotionEvents;
356bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.action.PrecisionDescriber;
3648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.Press;
3748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.Swipe;
3848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.ViewActions;
3948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.assertion.ViewAssertions;
406bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.core.deps.guava.base.Preconditions;
4148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.matcher.ViewMatchers;
426bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.v4.view.ViewCompat;
43b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Arakiimport android.support.v4.widget.NestedScrollView;
4448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.test.suitebuilder.annotation.MediumTest;
4548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.test.suitebuilder.annotation.SmallTest;
466bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.view.LayoutInflater;
476bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.view.MotionEvent;
4848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.view.View;
496bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.view.ViewConfiguration;
5048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.view.ViewGroup;
51b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Arakiimport android.widget.TextView;
52b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki
53ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikovimport org.hamcrest.Matcher;
54ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikovimport org.junit.Test;
5548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
5648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport static org.hamcrest.CoreMatchers.is;
5748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport static org.hamcrest.CoreMatchers.not;
5848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport static org.hamcrest.MatcherAssert.assertThat;
5948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
6048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakipublic class BottomSheetBehaviorTest extends
6148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        BaseInstrumentationTestCase<BottomSheetBehaviorActivity> {
6248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
6348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public static class Callback extends BottomSheetBehavior.BottomSheetCallback
6448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            implements IdlingResource {
6548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
6648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        private boolean mIsIdle;
6748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
6848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        private IdlingResource.ResourceCallback mResourceCallback;
6948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
7048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public Callback(BottomSheetBehavior behavior) {
7148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            behavior.setBottomSheetCallback(this);
7248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            int state = behavior.getState();
7348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mIsIdle = isIdleState(state);
7448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
7548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
7648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
77dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Araki        public void onStateChanged(@NonNull View bottomSheet,
78dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Araki                @BottomSheetBehavior.State int newState) {
7948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            boolean wasIdle = mIsIdle;
8048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mIsIdle = isIdleState(newState);
8148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            if (!wasIdle && mIsIdle && mResourceCallback != null) {
8248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                mResourceCallback.onTransitionToIdle();
8348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            }
8448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
8548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
8648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
87dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Araki        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
8848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
8948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
9048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
9148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public String getName() {
9248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            return Callback.class.getSimpleName();
9348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
9448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
9548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
9648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public boolean isIdleNow() {
9748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            return mIsIdle;
9848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
9948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
10048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
10148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public void registerIdleTransitionCallback(IdlingResource.ResourceCallback callback) {
10248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mResourceCallback = callback;
10348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
10448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
10548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        private boolean isIdleState(int state) {
10648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            return state != BottomSheetBehavior.STATE_DRAGGING &&
10748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    state != BottomSheetBehavior.STATE_SETTLING;
10848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
10948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
11048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
11148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
1126bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    /**
1136bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki     * This is like {@link GeneralSwipeAction}, but it does not send ACTION_UP at the end.
1146bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki     */
1156bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    private static class DragAction implements ViewAction {
1166bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1176bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private static final int STEPS = 10;
1186bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private static final int DURATION = 100;
1196bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1206bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final CoordinatesProvider mStart;
1216bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final CoordinatesProvider mEnd;
1226bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final PrecisionDescriber mPrecisionDescriber;
1236bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1246bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public DragAction(CoordinatesProvider start, CoordinatesProvider end,
1256bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                PrecisionDescriber precisionDescriber) {
1266bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mStart = start;
1276bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mEnd = end;
1286bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mPrecisionDescriber = precisionDescriber;
1296bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1306bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1316bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
1326bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public Matcher<View> getConstraints() {
1336bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return ViewMatchers.isDisplayed();
1346bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1356bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1366bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
1376bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public String getDescription() {
1386bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return "drag";
1396bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1406bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1416bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
1426bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public void perform(UiController uiController, View view) {
1436bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[] precision = mPrecisionDescriber.describePrecision();
1446bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[] start = mStart.calculateCoordinates(view);
1456bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[] end = mEnd.calculateCoordinates(view);
1466bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[][] steps = interpolate(start, end, STEPS);
1476bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            int delayBetweenMovements = DURATION / steps.length;
1486bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            // Down
1496bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            MotionEvent downEvent = MotionEvents.sendDown(uiController, start, precision).down;
1506bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            try {
1516bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                for (int i = 0; i < steps.length; i++) {
1526bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    // Wait
1536bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    long desiredTime = downEvent.getDownTime() + (long)(delayBetweenMovements * i);
1546bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    long timeUntilDesired = desiredTime - SystemClock.uptimeMillis();
1556bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    if (timeUntilDesired > 10L) {
1566bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        uiController.loopMainThreadForAtLeast(timeUntilDesired);
1576bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
1586bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    // Move
1596bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    if (!MotionEvents.sendMovement(uiController, downEvent, steps[i])) {
1606bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        MotionEvents.sendCancel(uiController, downEvent);
1616bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        throw new RuntimeException("Cannot drag: failed to send a move event.");
1626bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
1636bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    BottomSheetBehavior behavior = BottomSheetBehavior.from(view);
1646bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                }
1656bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                int duration = ViewConfiguration.getPressedStateDuration();
1666bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                if (duration > 0) {
1676bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    uiController.loopMainThreadForAtLeast((long) duration);
1686bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                }
1696bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            } finally {
1706bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                downEvent.recycle();
1716bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            }
1726bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1736bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1746bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private static float[][] interpolate(float[] start, float[] end, int steps) {
1756bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            Preconditions.checkElementIndex(1, start.length);
1766bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            Preconditions.checkElementIndex(1, end.length);
1776bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[][] res = new float[steps][2];
1786bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            for(int i = 1; i < steps + 1; ++i) {
1796bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                res[i - 1][0] = start[0] + (end[0] - start[0]) * (float)i / ((float)steps + 2.0F);
1806bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                res[i - 1][1] = start[1] + (end[1] - start[1]) * (float)i / ((float)steps + 2.0F);
1816bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            }
1826bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return res;
1836bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1846bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    }
1856bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1866bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    private static class AddViewAction implements ViewAction {
1876bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1886bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final int mLayout;
1896bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1906bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public AddViewAction(@LayoutRes int layout) {
1916bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mLayout = layout;
1926bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1936bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1946bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
1956bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public Matcher<View> getConstraints() {
1966bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return ViewMatchers.isAssignableFrom(ViewGroup.class);
1976bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1986bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1996bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
2006bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public String getDescription() {
2016bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return "add view";
2026bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2036bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2046bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
2056bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public void perform(UiController uiController, View view) {
2066bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            ViewGroup parent = (ViewGroup) view;
2076bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            View child = LayoutInflater.from(view.getContext()).inflate(mLayout, parent, false);
2086bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            parent.addView(child);
2096bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2106bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    }
2116bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
21248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private Callback mCallback;
21348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
21448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public BottomSheetBehaviorTest() {
21548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        super(BottomSheetBehaviorActivity.class);
21648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
21748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
21848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
21948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @SmallTest
22048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testInitialSetup() {
22148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        BottomSheetBehavior behavior = getBehavior();
22248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
22348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        CoordinatorLayout coordinatorLayout = getCoordinatorLayout();
22448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        ViewGroup bottomSheet = getBottomSheet();
22548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        assertThat(bottomSheet.getTop(),
22648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                is(coordinatorLayout.getHeight() - behavior.getPeekHeight()));
22748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
22848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
22948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
23048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
23148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testSetStateExpandedToCollapsed() {
23248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
23348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
23448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
23548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
23648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
23748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
23848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testSetStateHiddenToCollapsed() {
23948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_HIDDEN, not(ViewMatchers.isDisplayed()));
24048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
24148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
24248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
24348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
24448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
245e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki    public void testSetStateCollapsedToCollapsed() {
246e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
247e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki    }
248e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki
249e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki    @Test
250e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki    @MediumTest
25148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testSwipeDownToCollapse() {
25248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
25348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
254e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                .perform(DesignViewActions.withCustomConstraints(new GeneralSwipeAction(
2558609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        Swipe.FAST,
2568609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        // Manually calculate the starting coordinates to make sure that the touch
2578609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        // actually falls onto the view on Gingerbread
2588609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        new CoordinatesProvider() {
2598609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                            @Override
2608609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                            public float[] calculateCoordinates(View view) {
2618609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                int[] location = new int[2];
2628609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                view.getLocationInWindow(location);
2638609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                return new float[]{
2648609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                        view.getWidth() / 2,
2658609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                        location[1] + 1
2668609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                };
2678609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                            }
2688609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        },
269e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        // Manually calculate the ending coordinates to make sure that the bottom
270e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        // sheet is collapsed, not hidden
271e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        new CoordinatesProvider() {
272e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            @Override
273e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            public float[] calculateCoordinates(View view) {
274e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                BottomSheetBehavior behavior = getBehavior();
275e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                return new float[]{
276e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        // x: center of the bottom sheet
277e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        view.getWidth() / 2,
278e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        // y: just above the peek height
279e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        view.getHeight() - behavior.getPeekHeight()};
280e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            }
281e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        }, Press.FINGER), ViewMatchers.isDisplayingAtLeast(5)));
28248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        // Avoid a deadlock (b/26160710)
28348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        registerIdlingResourceCallback();
28448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
28548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
28648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
28748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
28848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
28948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
29048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
29148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
29248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
29348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
29448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
29548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testSwipeDownToHide() {
29648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
29748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(ViewActions.swipeDown(),
29848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                        ViewMatchers.isDisplayingAtLeast(5)));
29948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        // Avoid a deadlock (b/26160710)
300d7e25b5cfb70b867b7467acb613a984310a0c40dYuichi Araki        registerIdlingResourceCallback();
30148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
30248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
30348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(not(ViewMatchers.isDisplayed())));
30448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_HIDDEN));
30548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
30648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
30748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
30848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
30948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
31048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
311362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki    public void testSkipCollapsed() {
312362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        getBehavior().setSkipCollapsed(true);
313362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
314362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
315362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(new GeneralSwipeAction(
316362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        Swipe.FAST,
317362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // Manually calculate the starting coordinates to make sure that the touch
318362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // actually falls onto the view on Gingerbread
319362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        new CoordinatesProvider() {
320362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            @Override
321362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            public float[] calculateCoordinates(View view) {
322362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                int[] location = new int[2];
323362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                view.getLocationInWindow(location);
324362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                return new float[]{
325362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        view.getWidth() / 2,
326362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        location[1] + 1
327362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                };
328362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            }
329362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        },
330362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // Manually calculate the ending coordinates to make sure that the bottom
331362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // sheet is collapsed, not hidden
332362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        new CoordinatesProvider() {
333362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            @Override
334362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            public float[] calculateCoordinates(View view) {
335362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                BottomSheetBehavior behavior = getBehavior();
336362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                return new float[]{
337362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        // x: center of the bottom sheet
338362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        view.getWidth() / 2,
339362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        // y: just above the peek height
340362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        view.getHeight() - behavior.getPeekHeight()};
341362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            }
342362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        }, Press.FINGER), ViewMatchers.isDisplayingAtLeast(5)));
343362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        registerIdlingResourceCallback();
344362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        try {
345362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
346362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                    .check(ViewAssertions.matches(not(ViewMatchers.isDisplayed())));
347362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_HIDDEN));
348362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        } finally {
349362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki            unregisterIdlingResourceCallback();
350362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        }
351362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki    }
352362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki
353362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki    @Test
35448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
35548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testSwipeUpToExpand() {
35648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
35748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(
35848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                        new GeneralSwipeAction(Swipe.FAST,
359e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
360e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            @Override
361e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            public float[] calculateCoordinates(View view) {
362e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                return new float[]{view.getWidth() / 2, 0};
363e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            }
364e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        }, Press.FINGER),
36548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                        ViewMatchers.isDisplayingAtLeast(5)));
36648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        // Avoid a deadlock (b/26160710)
36748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        registerIdlingResourceCallback();
36848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
36948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
37048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
37148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_EXPANDED));
37248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
37348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
37448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
37548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
37648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
37717fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki    @Test
37817fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki    @MediumTest
379ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov    public void testInvisible() {
38017fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        // Make the bottomsheet invisible
381ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
38217fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            @Override
38317fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            public void run() {
38417fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                getBottomSheet().setVisibility(View.INVISIBLE);
38517fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
38617fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            }
38717fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        });
38817fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        // Swipe up as if to expand it
38917fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
39017fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(
39117fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                        new GeneralSwipeAction(Swipe.FAST,
39217fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                                GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
39317fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                            @Override
39417fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                            public float[] calculateCoordinates(View view) {
39517fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                                return new float[]{view.getWidth() / 2, 0};
39617fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                            }
39717fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                        }, Press.FINGER),
39817fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                        not(ViewMatchers.isDisplayed())));
39917fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        // Check that the bottom sheet stays the same collapsed state
400ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
40117fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            @Override
40217fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            public void run() {
40317fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
40417fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            }
40517fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        });
40617fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki    }
40717fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki
408b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki    @Test
409b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki    @MediumTest
410b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki    public void testNestedScroll() {
411b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        final ViewGroup bottomSheet = getBottomSheet();
412b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        final BottomSheetBehavior behavior = getBehavior();
413b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        final NestedScrollView scroll = new NestedScrollView(mActivityTestRule.getActivity());
414b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        // Set up nested scrolling area
415b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
416b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            @Override
417b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            public void run() {
418b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                bottomSheet.addView(scroll, new ViewGroup.LayoutParams(
419b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
420b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                TextView view = new TextView(mActivityTestRule.getActivity());
421b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                StringBuilder sb = new StringBuilder();
422b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                for (int i = 0; i < 500; ++i) {
423b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    sb.append("It is fine today. ");
424b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                }
425b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                view.setText(sb);
426b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                view.setOnClickListener(new View.OnClickListener() {
427b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    @Override
428b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    public void onClick(View v) {
429b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        // Do nothing
430b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    }
431b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                });
432b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                scroll.addView(view);
433b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
434b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                // The scroll offset is 0 at first
435b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                assertThat(scroll.getScrollY(), is(0));
436b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            }
437b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        });
438b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        // Swipe from the very bottom of the bottom sheet to the top edge of the screen so that the
439b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        // scrolling content is also scrolled
440b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.coordinator))
441b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                .perform(new GeneralSwipeAction(Swipe.FAST,
442b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        new CoordinatesProvider() {
443b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            @Override
444b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            public float[] calculateCoordinates(View view) {
445b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                                return new float[]{view.getWidth() / 2, view.getHeight() - 1};
446b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            }
447b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        },
448b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        new CoordinatesProvider() {
449b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            @Override
450b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            public float[] calculateCoordinates(View view) {
451b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                                return new float[]{view.getWidth() / 2, 1};
452b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            }
453b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        }, Press.FINGER));
454b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        registerIdlingResourceCallback();
455b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        try {
456b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
457b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
458b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
459b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                @Override
460b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                public void run() {
461b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_EXPANDED));
462b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    // This confirms that the nested scrolling area was scrolled continuously after
463b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    // the bottom sheet is expanded.
464b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    assertThat(scroll.getScrollY(), is(not(0)));
465b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                }
466b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            });
467b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        } finally {
468b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            unregisterIdlingResourceCallback();
469b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        }
470b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki    }
471b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki
4726bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @Test
4736bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @MediumTest
474912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki    public void testDragOutside() {
475912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        // Swipe up outside of the bottom sheet
476912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.coordinator))
477912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                .perform(DesignViewActions.withCustomConstraints(
478912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                        new GeneralSwipeAction(Swipe.FAST,
479912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                // Just above the bottom sheet
480912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                new CoordinatesProvider() {
481912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    @Override
482912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    public float[] calculateCoordinates(View view) {
483912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                        return new float[]{
484912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                                view.getWidth() / 2,
485912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                                view.getHeight() - getBehavior().getPeekHeight() - 9
486912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                        };
487912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    }
488912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                },
489912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                // Top of the CoordinatorLayout
490912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                new CoordinatesProvider() {
491912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    @Override
492912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    public float[] calculateCoordinates(View view) {
493912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                        return new float[]{view.getWidth() / 2, 1};
494912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    }
495912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                }, Press.FINGER),
496912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                        ViewMatchers.isDisplayed()));
497912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        // Avoid a deadlock (b/26160710)
498912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        registerIdlingResourceCallback();
499912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        try {
500912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
501912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
502912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            // The bottom sheet should remain collapsed
503912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
504912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        } finally {
505912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            unregisterIdlingResourceCallback();
506912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        }
507912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki    }
508912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki
5096bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @Test
5106bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @MediumTest
5116bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    public void testLayoutWhileDragging() {
5126bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
5136bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Drag (and not release)
5146bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .perform(new DragAction(
5156bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        GeneralLocation.VISIBLE_CENTER,
5166bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        GeneralLocation.TOP_CENTER,
5176bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        Press.FINGER))
5186bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Check that the bottom sheet is in STATE_DRAGGING
5196bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .check(new ViewAssertion() {
5206bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    @Override
5216bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    public void check(View view, NoMatchingViewException e) {
5226bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(view, is(ViewMatchers.isDisplayed()));
5236bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        BottomSheetBehavior behavior = BottomSheetBehavior.from(view);
5246bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_DRAGGING));
5256bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
5266bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                })
5276bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Add a new view
5286bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .perform(new AddViewAction(R.layout.frame_layout))
5296bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Check that the newly added view is properly laid out
5306bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .check(new ViewAssertion() {
5316bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    @Override
5326bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    public void check(View view, NoMatchingViewException e) {
5336bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        ViewGroup parent = (ViewGroup) view;
5346bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(parent.getChildCount(), is(1));
5356bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        View child = parent.getChildAt(0);
5366bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(ViewCompat.isLaidOut(child), is(true));
5376bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
5386bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                });
5396bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    }
5406bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
541ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov    private void checkSetState(final int state, Matcher<View> matcher) {
54248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        registerIdlingResourceCallback();
54348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
544ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
545ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                @Override
546ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                public void run() {
547ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                    getBehavior().setState(state);
548ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                }
549ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov            });
55048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
55148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(matcher));
55248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(state));
55348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
55448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
55548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
55648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
55748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
55848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private void registerIdlingResourceCallback() {
55948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        // TODO(yaraki): Move this to setUp() when b/26160710 is fixed
56048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        mCallback = new Callback(getBehavior());
56148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.registerIdlingResources(mCallback);
56248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
56348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
56448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private void unregisterIdlingResourceCallback() {
56548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        if (mCallback != null) {
56648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.unregisterIdlingResources(mCallback);
56748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mCallback = null;
56848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
56948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
57048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
57148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private ViewGroup getBottomSheet() {
572ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        return mActivityTestRule.getActivity().mBottomSheet;
57348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
57448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
57548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private BottomSheetBehavior getBehavior() {
576ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        return mActivityTestRule.getActivity().mBehavior;
57748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
57848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
57948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private CoordinatorLayout getCoordinatorLayout() {
580ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        return mActivityTestRule.getActivity().mCoordinatorLayout;
58148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
58248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
58348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki}
584