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
198f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Arakiimport static org.hamcrest.CoreMatchers.is;
208f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Arakiimport static org.hamcrest.CoreMatchers.not;
218f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Arakiimport static org.hamcrest.MatcherAssert.assertThat;
22e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Arakiimport static org.hamcrest.Matchers.greaterThanOrEqualTo;
23e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Arakiimport static org.hamcrest.Matchers.lessThanOrEqualTo;
24acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikasimport static org.junit.Assert.fail;
258f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
2647a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguireimport android.content.Context;
276bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.os.SystemClock;
286bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.annotation.LayoutRes;
29dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Arakiimport android.support.annotation.NonNull;
3048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.design.test.R;
3148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.Espresso;
3248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.IdlingResource;
336bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.NoMatchingViewException;
346bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.UiController;
356bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.ViewAction;
366bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.ViewAssertion;
37e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Arakiimport android.support.test.espresso.action.CoordinatesProvider;
3848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.GeneralLocation;
3948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.GeneralSwipeAction;
406bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.action.MotionEvents;
416bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.action.PrecisionDescriber;
4248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.Press;
4348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.Swipe;
4448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.action.ViewActions;
4548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.assertion.ViewAssertions;
466bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.test.espresso.core.deps.guava.base.Preconditions;
4748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.support.test.espresso.matcher.ViewMatchers;
48754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.MediumTest;
49754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest;
506bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.support.v4.view.ViewCompat;
51b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Arakiimport android.support.v4.widget.NestedScrollView;
526bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.view.LayoutInflater;
536bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.view.MotionEvent;
5448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.view.View;
556bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Arakiimport android.view.ViewConfiguration;
5648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakiimport android.view.ViewGroup;
57b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki
58ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikovimport org.hamcrest.Matcher;
596cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Arakiimport org.hamcrest.Matchers;
60ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikovimport org.junit.Test;
6148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
6248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Arakipublic class BottomSheetBehaviorTest extends
6348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        BaseInstrumentationTestCase<BottomSheetBehaviorActivity> {
6448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
6548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public static class Callback extends BottomSheetBehavior.BottomSheetCallback
6648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            implements IdlingResource {
6748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
6848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        private boolean mIsIdle;
6948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
7048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        private IdlingResource.ResourceCallback mResourceCallback;
7148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
7248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public Callback(BottomSheetBehavior behavior) {
7348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            behavior.setBottomSheetCallback(this);
7448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            int state = behavior.getState();
7548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mIsIdle = isIdleState(state);
7648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
7748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
7848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
79dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Araki        public void onStateChanged(@NonNull View bottomSheet,
80dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Araki                @BottomSheetBehavior.State int newState) {
8148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            boolean wasIdle = mIsIdle;
8248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mIsIdle = isIdleState(newState);
8348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            if (!wasIdle && mIsIdle && mResourceCallback != null) {
8448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                mResourceCallback.onTransitionToIdle();
8548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            }
8648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
8748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
8848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
89dffd8d4be91b2e5e0ce66ad96867182db0c02fd0Yuichi Araki        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
90e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki            assertThat(slideOffset, is(greaterThanOrEqualTo(-1f)));
91e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki            assertThat(slideOffset, is(lessThanOrEqualTo(1f)));
9248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
9348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
9448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
9548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public String getName() {
9648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            return Callback.class.getSimpleName();
9748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
9848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
9948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
10048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public boolean isIdleNow() {
10148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            return mIsIdle;
10248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
10348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
10448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        @Override
10548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        public void registerIdleTransitionCallback(IdlingResource.ResourceCallback callback) {
10648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mResourceCallback = callback;
10748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
10848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
10948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        private boolean isIdleState(int state) {
11048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            return state != BottomSheetBehavior.STATE_DRAGGING &&
11148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    state != BottomSheetBehavior.STATE_SETTLING;
11248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
11348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
11448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
11548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
1166bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    /**
1178f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki     * Wait for a FAB to change its visibility (either shown or hidden).
1188f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki     */
1198f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    private static class OnVisibilityChangedListener extends
1208f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            FloatingActionButton.OnVisibilityChangedListener implements IdlingResource {
1218f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1228f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        private final boolean mShown;
1238f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        private boolean mIsIdle;
1248f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        private ResourceCallback mResourceCallback;
1258f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1268f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        OnVisibilityChangedListener(boolean shown) {
1278f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            mShown = shown;
1288f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
1298f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1308f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        private void transitionToIdle() {
1318f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            if (!mIsIdle) {
1328f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                mIsIdle = true;
1338f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                if (mResourceCallback != null) {
1348f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                    mResourceCallback.onTransitionToIdle();
1358f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                }
1368f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            }
1378f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
1388f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1398f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        @Override
1408f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        public void onShown(FloatingActionButton fab) {
1418f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            if (mShown) {
1428f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                transitionToIdle();
1438f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            }
1448f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
1458f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1468f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        @Override
1478f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        public void onHidden(FloatingActionButton fab) {
1488f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            if (!mShown) {
1498f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                transitionToIdle();
1508f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            }
1518f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
1528f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1538f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        @Override
1548f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        public String getName() {
1558f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            return OnVisibilityChangedListener.class.getSimpleName();
1568f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
1578f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1588f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        @Override
1598f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        public boolean isIdleNow() {
1608f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            return mIsIdle;
1618f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
1628f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1638f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        @Override
1648f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
1658f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            mResourceCallback = resourceCallback;
1668f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
1678f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    }
1688f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
1698f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    /**
1706bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki     * This is like {@link GeneralSwipeAction}, but it does not send ACTION_UP at the end.
1716bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki     */
1726bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    private static class DragAction implements ViewAction {
1736bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1746bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private static final int STEPS = 10;
1756bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private static final int DURATION = 100;
1766bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1776bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final CoordinatesProvider mStart;
1786bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final CoordinatesProvider mEnd;
1796bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final PrecisionDescriber mPrecisionDescriber;
1806bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1816bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public DragAction(CoordinatesProvider start, CoordinatesProvider end,
1826bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                PrecisionDescriber precisionDescriber) {
1836bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mStart = start;
1846bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mEnd = end;
1856bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mPrecisionDescriber = precisionDescriber;
1866bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1876bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1886bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
1896bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public Matcher<View> getConstraints() {
1906cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki            return Matchers.any(View.class);
1916bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1926bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1936bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
1946bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public String getDescription() {
1956bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return "drag";
1966bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
1976bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
1986bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
1996bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public void perform(UiController uiController, View view) {
2006bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[] precision = mPrecisionDescriber.describePrecision();
2016bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[] start = mStart.calculateCoordinates(view);
2026bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[] end = mEnd.calculateCoordinates(view);
2036bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[][] steps = interpolate(start, end, STEPS);
2046bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            int delayBetweenMovements = DURATION / steps.length;
2056bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            // Down
2066bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            MotionEvent downEvent = MotionEvents.sendDown(uiController, start, precision).down;
2076bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            try {
2086bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                for (int i = 0; i < steps.length; i++) {
2096bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    // Wait
2106bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    long desiredTime = downEvent.getDownTime() + (long)(delayBetweenMovements * i);
2116bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    long timeUntilDesired = desiredTime - SystemClock.uptimeMillis();
2126bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    if (timeUntilDesired > 10L) {
2136bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        uiController.loopMainThreadForAtLeast(timeUntilDesired);
2146bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
2156bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    // Move
2166bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    if (!MotionEvents.sendMovement(uiController, downEvent, steps[i])) {
2176bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        MotionEvents.sendCancel(uiController, downEvent);
2186bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        throw new RuntimeException("Cannot drag: failed to send a move event.");
2196bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
2206bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                }
2216bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                int duration = ViewConfiguration.getPressedStateDuration();
2226bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                if (duration > 0) {
2236bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    uiController.loopMainThreadForAtLeast((long) duration);
2246bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                }
2256bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            } finally {
2266bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                downEvent.recycle();
2276bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            }
2286bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2296bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2306bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private static float[][] interpolate(float[] start, float[] end, int steps) {
2316bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            Preconditions.checkElementIndex(1, start.length);
2326bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            Preconditions.checkElementIndex(1, end.length);
2336bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            float[][] res = new float[steps][2];
2346bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            for(int i = 1; i < steps + 1; ++i) {
2356bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                res[i - 1][0] = start[0] + (end[0] - start[0]) * (float)i / ((float)steps + 2.0F);
2366bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                res[i - 1][1] = start[1] + (end[1] - start[1]) * (float)i / ((float)steps + 2.0F);
2376bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            }
2386bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return res;
2396bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2406bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    }
2416bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2426bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    private static class AddViewAction implements ViewAction {
2436bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2446bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        private final int mLayout;
2456bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2466bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public AddViewAction(@LayoutRes int layout) {
2476bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            mLayout = layout;
2486bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2496bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2506bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
2516bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public Matcher<View> getConstraints() {
2526bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return ViewMatchers.isAssignableFrom(ViewGroup.class);
2536bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2546bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2556bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
2566bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public String getDescription() {
2576bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            return "add view";
2586bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2596bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
2606bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        @Override
2616bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        public void perform(UiController uiController, View view) {
2626bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            ViewGroup parent = (ViewGroup) view;
2636bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            View child = LayoutInflater.from(view.getContext()).inflate(mLayout, parent, false);
2646bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki            parent.addView(child);
2656bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        }
2666bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    }
2676bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
26848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private Callback mCallback;
26948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
27048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public BottomSheetBehaviorTest() {
27148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        super(BottomSheetBehaviorActivity.class);
27248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
27348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
27448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
27548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @SmallTest
27648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testInitialSetup() {
27748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        BottomSheetBehavior behavior = getBehavior();
27848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
27948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        CoordinatorLayout coordinatorLayout = getCoordinatorLayout();
28048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        ViewGroup bottomSheet = getBottomSheet();
28148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        assertThat(bottomSheet.getTop(),
28248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                is(coordinatorLayout.getHeight() - behavior.getPeekHeight()));
28348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
28448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
28548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
28648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
287acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas    public void testSetStateExpandedToCollapsed() throws Throwable {
28848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
28948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
29048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
29148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
29248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
29348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
294acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas    public void testSetStateHiddenToCollapsed() throws Throwable {
29548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_HIDDEN, not(ViewMatchers.isDisplayed()));
29648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
29748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
29848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
29948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
30048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
301acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas    public void testSetStateCollapsedToCollapsed() throws Throwable {
302e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
303e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki    }
304e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki
305e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki    @Test
306e0573dfb5896eb66e266ea366d8c58437c739c95Yuichi Araki    @MediumTest
307acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas    public void testSwipeDownToCollapse() throws Throwable {
30848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
30948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
310e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                .perform(DesignViewActions.withCustomConstraints(new GeneralSwipeAction(
3118609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        Swipe.FAST,
3128609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        // Manually calculate the starting coordinates to make sure that the touch
3138609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        // actually falls onto the view on Gingerbread
3148609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        new CoordinatesProvider() {
3158609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                            @Override
3168609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                            public float[] calculateCoordinates(View view) {
3178609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                int[] location = new int[2];
3188609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                view.getLocationInWindow(location);
3198609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                return new float[]{
3208609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                        view.getWidth() / 2,
3218609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                        location[1] + 1
3228609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                                };
3238609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                            }
3248609a20bfe8d61704facb9c18836db5b3a754105Yuichi Araki                        },
325e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        // Manually calculate the ending coordinates to make sure that the bottom
326e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        // sheet is collapsed, not hidden
327e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        new CoordinatesProvider() {
328e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            @Override
329e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            public float[] calculateCoordinates(View view) {
330e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                BottomSheetBehavior behavior = getBehavior();
331e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                return new float[]{
332e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        // x: center of the bottom sheet
333e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        view.getWidth() / 2,
334e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        // y: just above the peek height
335e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                        view.getHeight() - behavior.getPeekHeight()};
336e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            }
337e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        }, Press.FINGER), ViewMatchers.isDisplayingAtLeast(5)));
33848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        registerIdlingResourceCallback();
33948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
34048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
34148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
34248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
34348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
34448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
34548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
34648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
34748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
34848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
34948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
35048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testSwipeDownToHide() {
35148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
35248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(ViewActions.swipeDown(),
35348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                        ViewMatchers.isDisplayingAtLeast(5)));
354d7e25b5cfb70b867b7467acb613a984310a0c40dYuichi Araki        registerIdlingResourceCallback();
35548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
35648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
35748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(not(ViewMatchers.isDisplayed())));
35848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_HIDDEN));
35948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
36048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
36148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
36248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
36348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
36448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @Test
365cea79d768278d2c55f89263f99b8860cc5447aaaAurimas Liutikas    @MediumTest
366acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas    public void testSkipCollapsed() throws Throwable {
367362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        getBehavior().setSkipCollapsed(true);
368362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
369362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
370362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(new GeneralSwipeAction(
371362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        Swipe.FAST,
372362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // Manually calculate the starting coordinates to make sure that the touch
373362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // actually falls onto the view on Gingerbread
374362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        new CoordinatesProvider() {
375362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            @Override
376362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            public float[] calculateCoordinates(View view) {
377362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                int[] location = new int[2];
378362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                view.getLocationInWindow(location);
379362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                return new float[]{
380362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        view.getWidth() / 2,
381362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        location[1] + 1
382362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                };
383362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            }
384362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        },
385362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // Manually calculate the ending coordinates to make sure that the bottom
386362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        // sheet is collapsed, not hidden
387362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        new CoordinatesProvider() {
388362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            @Override
389362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            public float[] calculateCoordinates(View view) {
390362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                BottomSheetBehavior behavior = getBehavior();
391362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                return new float[]{
392362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        // x: center of the bottom sheet
393362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        view.getWidth() / 2,
394362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        // y: just above the peek height
395362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                                        view.getHeight() - behavior.getPeekHeight()};
396362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                            }
397362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                        }, Press.FINGER), ViewMatchers.isDisplayingAtLeast(5)));
398362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        registerIdlingResourceCallback();
399362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        try {
400362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
401362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki                    .check(ViewAssertions.matches(not(ViewMatchers.isDisplayed())));
402362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_HIDDEN));
403362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        } finally {
404362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki            unregisterIdlingResourceCallback();
405362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki        }
406362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki    }
407362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki
408362585b01e5ca19d1c58e4b152ad0a863b5f6d91Yuichi Araki    @Test
40948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    @MediumTest
41048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    public void testSwipeUpToExpand() {
41148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
41248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(
41348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                        new GeneralSwipeAction(Swipe.FAST,
414e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
415e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            @Override
416e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            public float[] calculateCoordinates(View view) {
417e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                                return new float[]{view.getWidth() / 2, 0};
418e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                            }
419e1b07825c14a3dee006a0489fd8e2c3060d9884fYuichi Araki                        }, Press.FINGER),
42048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                        ViewMatchers.isDisplayingAtLeast(5)));
42148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        registerIdlingResourceCallback();
42248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
42348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
42448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
42548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_EXPANDED));
42648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
42748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
42848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
42948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
43048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
43117fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki    @Test
43217fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki    @MediumTest
43342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testInvisible() throws Throwable {
43417fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        // Make the bottomsheet invisible
43542e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
43617fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            @Override
43717fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            public void run() {
43817fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                getBottomSheet().setVisibility(View.INVISIBLE);
43917fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
44017fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            }
44117fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        });
44217fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        // Swipe up as if to expand it
44317fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
44417fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                .perform(DesignViewActions.withCustomConstraints(
44517fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                        new GeneralSwipeAction(Swipe.FAST,
44617fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                                GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
44717fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                            @Override
44817fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                            public float[] calculateCoordinates(View view) {
44917fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                                return new float[]{view.getWidth() / 2, 0};
45017fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                            }
45117fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                        }, Press.FINGER),
45217fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                        not(ViewMatchers.isDisplayed())));
45317fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        // Check that the bottom sheet stays the same collapsed state
45442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
45517fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            @Override
45617fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            public void run() {
45717fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki                assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
45817fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki            }
45917fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki        });
46017fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki    }
46117fc77f5ca7c91daeab2e110669d9baf9d1a8090Yuichi Araki
462b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki    @Test
463b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki    @MediumTest
46442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testInvisibleThenVisible() throws Throwable {
46542e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
4666cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki            @Override
4676cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki            public void run() {
4686cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                // The bottom sheet is initially invisible
4696cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                getBottomSheet().setVisibility(View.INVISIBLE);
4706cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                // Then it becomes visible when the CoL is touched
4716cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                getCoordinatorLayout().setOnTouchListener(new View.OnTouchListener() {
4726cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                    @Override
4736cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                    public boolean onTouch(View view, MotionEvent e) {
4746cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        if (e.getAction() == MotionEvent.ACTION_DOWN) {
4756cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                            getBottomSheet().setVisibility(View.VISIBLE);
4766cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                            return true;
4776cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        }
4786cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        return false;
4796cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                    }
4806cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                });
4816cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
4826cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki            }
4836cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki        });
4846cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki        // Drag over the CoL
4856cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.coordinator))
4866cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                // Drag (and not release)
4876cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                .perform(new DragAction(
4886cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        GeneralLocation.BOTTOM_CENTER,
4896cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        GeneralLocation.TOP_CENTER,
4906cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        Press.FINGER))
4916cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                .check(new ViewAssertion() {
4926cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                    @Override
4936cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                    public void check(View view, NoMatchingViewException e) {
4946cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        // The bottom sheet should not react to the touch events
4956cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        assertThat(getBottomSheet(), is(ViewMatchers.isDisplayed()));
4966cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                        assertThat(getBehavior().getState(),
4976cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                                is(BottomSheetBehavior.STATE_COLLAPSED));
4986cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                    }
4996cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki                });
5006cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki    }
5016cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki
5026cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki    @Test
5036cb44089e8c7a5abf8aa7a0cdff3cb6888790708Yuichi Araki    @MediumTest
50442e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testNestedScroll() throws Throwable {
505b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        final ViewGroup bottomSheet = getBottomSheet();
506b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        final BottomSheetBehavior behavior = getBehavior();
507b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        final NestedScrollView scroll = new NestedScrollView(mActivityTestRule.getActivity());
508b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        // Set up nested scrolling area
50942e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
510b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            @Override
511b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            public void run() {
512b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                bottomSheet.addView(scroll, new ViewGroup.LayoutParams(
513b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
5148be0c0d616f707a5c1e794a830a44bce7c1d75dfYuichi Araki                final View view = new View(mActivityTestRule.getActivity());
5158be0c0d616f707a5c1e794a830a44bce7c1d75dfYuichi Araki                // Make sure that the NestedScrollView is always scrollable
5168be0c0d616f707a5c1e794a830a44bce7c1d75dfYuichi Araki                view.setMinimumHeight(bottomSheet.getHeight() + 1000);
517b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                scroll.addView(view);
518b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
519b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                // The scroll offset is 0 at first
520b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                assertThat(scroll.getScrollY(), is(0));
521b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            }
522b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        });
523b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        // Swipe from the very bottom of the bottom sheet to the top edge of the screen so that the
524b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        // scrolling content is also scrolled
525b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.coordinator))
526a7a51521e1ecd8a009e4bd618520af05d9917671Yuichi Araki                .perform(new GeneralSwipeAction(Swipe.SLOW,
527b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        new CoordinatesProvider() {
528b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            @Override
529b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            public float[] calculateCoordinates(View view) {
530b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                                return new float[]{view.getWidth() / 2, view.getHeight() - 1};
531b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            }
532b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        },
533b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        new CoordinatesProvider() {
534b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            @Override
535b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            public float[] calculateCoordinates(View view) {
536b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                                return new float[]{view.getWidth() / 2, 1};
537b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                            }
538b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                        }, Press.FINGER));
539b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        registerIdlingResourceCallback();
540b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        try {
541b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
542b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
543acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas            mActivityTestRule.runOnUiThread(new Runnable() {
544b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                @Override
545b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                public void run() {
546b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_EXPANDED));
547b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    // This confirms that the nested scrolling area was scrolled continuously after
548b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    // the bottom sheet is expanded.
549b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                    assertThat(scroll.getScrollY(), is(not(0)));
550b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki                }
551b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            });
552b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        } finally {
553b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki            unregisterIdlingResourceCallback();
554b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki        }
555b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki    }
556b1eb1357ec33343b7271a6d3876ac4af84e15510Yuichi Araki
5576bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @Test
5586bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @MediumTest
559912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki    public void testDragOutside() {
560912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        // Swipe up outside of the bottom sheet
561912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.coordinator))
562912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                .perform(DesignViewActions.withCustomConstraints(
563912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                        new GeneralSwipeAction(Swipe.FAST,
564912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                // Just above the bottom sheet
565912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                new CoordinatesProvider() {
566912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    @Override
567912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    public float[] calculateCoordinates(View view) {
568912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                        return new float[]{
569912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                                view.getWidth() / 2,
570912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                                view.getHeight() - getBehavior().getPeekHeight() - 9
571912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                        };
572912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    }
573912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                },
574912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                // Top of the CoordinatorLayout
575912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                new CoordinatesProvider() {
576912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    @Override
577912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    public float[] calculateCoordinates(View view) {
578912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                        return new float[]{view.getWidth() / 2, 1};
579912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                    }
580912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                                }, Press.FINGER),
581912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                        ViewMatchers.isDisplayed()));
582912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        registerIdlingResourceCallback();
583912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        try {
584912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
585912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
586912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            // The bottom sheet should remain collapsed
587912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
588912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        } finally {
589912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki            unregisterIdlingResourceCallback();
590912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki        }
591912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki    }
592912d40d353f5b6d0a4fa18740ca40aa4b25b0f9cYuichi Araki
5936bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @Test
5946bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    @MediumTest
5956bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    public void testLayoutWhileDragging() {
5966bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki        Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
5976bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Drag (and not release)
5986bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .perform(new DragAction(
5996bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        GeneralLocation.VISIBLE_CENTER,
6006bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        GeneralLocation.TOP_CENTER,
6016bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        Press.FINGER))
6026bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Check that the bottom sheet is in STATE_DRAGGING
6036bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .check(new ViewAssertion() {
6046bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    @Override
6056bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    public void check(View view, NoMatchingViewException e) {
6066bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(view, is(ViewMatchers.isDisplayed()));
6076bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        BottomSheetBehavior behavior = BottomSheetBehavior.from(view);
6086bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_DRAGGING));
6096bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
6106bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                })
6116bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Add a new view
6126bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .perform(new AddViewAction(R.layout.frame_layout))
6136bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                // Check that the newly added view is properly laid out
6146bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                .check(new ViewAssertion() {
6156bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    @Override
6166bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    public void check(View view, NoMatchingViewException e) {
6176bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        ViewGroup parent = (ViewGroup) view;
6186bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(parent.getChildCount(), is(1));
6196bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        View child = parent.getChildAt(0);
6206bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                        assertThat(ViewCompat.isLaidOut(child), is(true));
6216bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                    }
6226bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki                });
6236bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki    }
6246bef5a31fd4bbfc70b21c0f694bcb272e88400a3Yuichi Araki
6258f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    @Test
626cea79d768278d2c55f89263f99b8860cc5447aaaAurimas Liutikas    @MediumTest
6278f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    public void testFabVisibility() {
6288f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        withFabVisibilityChange(false, new Runnable() {
6298f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            @Override
6308f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            public void run() {
631acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                try {
632acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                    checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
633acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                } catch (Throwable throwable) {
634acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                    fail(throwable.getMessage());
635acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                }
6368f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            }
6378f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        });
6388f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        withFabVisibilityChange(true, new Runnable() {
6398f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            @Override
6408f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            public void run() {
641acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                try {
642acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                    checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
643acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                } catch (Throwable throwable) {
644acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                    fail(throwable.getMessage());
645acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas                }
6468f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            }
6478f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        });
6488f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    }
6498f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
65001ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki    @Test
651cea79d768278d2c55f89263f99b8860cc5447aaaAurimas Liutikas    @MediumTest
65242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testAutoPeekHeight() throws Throwable {
65342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
65401ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki            @Override
65501ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki            public void run() {
65601ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki                getBehavior().setPeekHeight(BottomSheetBehavior.PEEK_HEIGHT_AUTO);
65701ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki            }
65801ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki        });
65942e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
66001ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki            @Override
66101ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki            public void run() {
662a7a51521e1ecd8a009e4bd618520af05d9917671Yuichi Araki                CoordinatorLayout col = getCoordinatorLayout();
66301ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki                assertThat(getBottomSheet().getTop(),
664a7a51521e1ecd8a009e4bd618520af05d9917671Yuichi Araki                        is(Math.min(col.getWidth() * 9 / 16,
665a7a51521e1ecd8a009e4bd618520af05d9917671Yuichi Araki                                col.getHeight() - getBehavior().getPeekHeightMin())));
66601ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki            }
66701ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki        });
66801ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki    }
66901ca48bfc8ad80dae2e2809420af1e4b80dd4ae5Yuichi Araki
670bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki    @Test
671e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki    @MediumTest
67242e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas    public void testAutoPeekHeightHide() throws Throwable {
67342e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityTestRule.runOnUiThread(new Runnable() {
674e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki            @Override
675e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki            public void run() {
676e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki                getBehavior().setHideable(true);
677e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki                getBehavior().setPeekHeight(0);
678e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki                getBehavior().setPeekHeight(BottomSheetBehavior.PEEK_HEIGHT_AUTO);
679e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki            }
680e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki        });
681e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki        checkSetState(BottomSheetBehavior.STATE_HIDDEN, not(ViewMatchers.isDisplayed()));
682e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki    }
683e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki
684e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki    @Test
685cea79d768278d2c55f89263f99b8860cc5447aaaAurimas Liutikas    @MediumTest
686acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas    public void testDynamicContent() throws Throwable {
687bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki        registerIdlingResourceCallback();
688bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki        try {
689acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas            mActivityTestRule.runOnUiThread(new Runnable() {
690bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                @Override
691bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                public void run() {
692bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    ViewGroup.LayoutParams params = getBottomSheet().getLayoutParams();
693bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
694bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    getBottomSheet().setLayoutParams(params);
695bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    View view = new View(getBottomSheet().getContext());
696bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    int size = getBehavior().getPeekHeight() * 2;
697bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    getBottomSheet().addView(view, new ViewGroup.LayoutParams(size, size));
698bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    assertThat(getBottomSheet().getChildCount(), is(1));
699bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    // Shrink the content height.
700bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    ViewGroup.LayoutParams lp = view.getLayoutParams();
701bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    lp.height = (int) (size * 0.8);
702bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    view.setLayoutParams(lp);
703bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    // Immediately expand the bottom sheet.
704bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
705bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                }
706bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki            });
707bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
708bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki                    .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
709bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki            assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_EXPANDED));
710bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki            // Make sure that the bottom sheet is not floating above the bottom.
711bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki            assertThat(getBottomSheet().getBottom(), is(getCoordinatorLayout().getBottom()));
712bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki        } finally {
713bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki            unregisterIdlingResourceCallback();
714bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki        }
715bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki    }
716bfd8da2625c8198bd7d6884acb9f4b73ff35a2afYuichi Araki
7175347bf6b48710e49eb2058403329bf821883959fYuichi Araki    @Test
7185347bf6b48710e49eb2058403329bf821883959fYuichi Araki    @MediumTest
7195347bf6b48710e49eb2058403329bf821883959fYuichi Araki    public void testExpandedPeekHeight() throws Throwable {
7205347bf6b48710e49eb2058403329bf821883959fYuichi Araki        mActivityTestRule.runOnUiThread(new Runnable() {
7215347bf6b48710e49eb2058403329bf821883959fYuichi Araki            @Override
7225347bf6b48710e49eb2058403329bf821883959fYuichi Araki            public void run() {
7235347bf6b48710e49eb2058403329bf821883959fYuichi Araki                // Make the peek height as tall as the bottom sheet.
7245347bf6b48710e49eb2058403329bf821883959fYuichi Araki                BottomSheetBehavior behavior = getBehavior();
7255347bf6b48710e49eb2058403329bf821883959fYuichi Araki                behavior.setPeekHeight(getBottomSheet().getHeight());
7265347bf6b48710e49eb2058403329bf821883959fYuichi Araki                assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
7275347bf6b48710e49eb2058403329bf821883959fYuichi Araki            }
7285347bf6b48710e49eb2058403329bf821883959fYuichi Araki        });
7295347bf6b48710e49eb2058403329bf821883959fYuichi Araki        // Both of these will not animate the sheet , but the state should be changed.
7305347bf6b48710e49eb2058403329bf821883959fYuichi Araki        checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
7315347bf6b48710e49eb2058403329bf821883959fYuichi Araki        checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
7325347bf6b48710e49eb2058403329bf821883959fYuichi Araki    }
7335347bf6b48710e49eb2058403329bf821883959fYuichi Araki
73452151cc4906f7a1f5925fcee872fa915606590c7Chris Banes    @Test
73552151cc4906f7a1f5925fcee872fa915606590c7Chris Banes    @SmallTest
73647a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire    public void testFindScrollingChildEnabled() {
73747a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        Context context = mActivityTestRule.getActivity();
73847a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        NestedScrollView disabledParent = new NestedScrollView(context);
73947a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        disabledParent.setNestedScrollingEnabled(false);
74047a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        NestedScrollView enabledChild = new NestedScrollView(context);
74147a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        enabledChild.setNestedScrollingEnabled(true);
74247a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        disabledParent.addView(enabledChild);
74347a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire
74447a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        View scrollingChild = getBehavior().findScrollingChild(disabledParent);
74547a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire        assertThat(scrollingChild, is((View) enabledChild));
74647a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire    }
74747a4a1aac79f0ad2279c7a3eeaad1673646f4d81Kassim Maguire
748acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas    private void checkSetState(final int state, Matcher<View> matcher) throws Throwable {
74948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        registerIdlingResourceCallback();
75048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        try {
751acf7e812c17c73a27c5e5973dde80e1c59712469Aurimas Liutikas            mActivityTestRule.runOnUiThread(new Runnable() {
752ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                @Override
753ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                public void run() {
754ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                    getBehavior().setState(state);
755ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov                }
756ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov            });
75748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
75848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki                    .check(ViewAssertions.matches(matcher));
75948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            assertThat(getBehavior().getState(), is(state));
76048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        } finally {
76148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            unregisterIdlingResourceCallback();
76248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
76348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
76448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
76548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private void registerIdlingResourceCallback() {
766e9864e3df037a1a5610752c6addc4d71e6b292c7Yuichi Araki        // This cannot be done in setUp(), or swiping action cannot be executed.
76748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        mCallback = new Callback(getBehavior());
76848cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        Espresso.registerIdlingResources(mCallback);
76948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
77048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
77148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private void unregisterIdlingResourceCallback() {
77248cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        if (mCallback != null) {
77348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            Espresso.unregisterIdlingResources(mCallback);
77448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki            mCallback = null;
77548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki        }
77648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
77748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
7788f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    private void withFabVisibilityChange(boolean shown, Runnable action) {
7798f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        OnVisibilityChangedListener listener = new OnVisibilityChangedListener(shown);
7808f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        CoordinatorLayout.LayoutParams lp =
7818f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                (CoordinatorLayout.LayoutParams) mActivityTestRule.getActivity().mFab
7828f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki                        .getLayoutParams();
7838f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        FloatingActionButton.Behavior behavior = (FloatingActionButton.Behavior) lp.getBehavior();
7848f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        behavior.setInternalAutoHideListener(listener);
7858f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        Espresso.registerIdlingResources(listener);
7868f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        try {
7878f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            action.run();
7888f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        } finally {
7898f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki            Espresso.unregisterIdlingResources(listener);
7908f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki        }
7918f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki    }
7928f8034a4204fc10c4805758051f0cec2159c0e40Yuichi Araki
79348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private ViewGroup getBottomSheet() {
794ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        return mActivityTestRule.getActivity().mBottomSheet;
79548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
79648cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
79748cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private BottomSheetBehavior getBehavior() {
798ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        return mActivityTestRule.getActivity().mBehavior;
79948cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
80048cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
80148cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    private CoordinatorLayout getCoordinatorLayout() {
802ee3e8053ef6b55e31fa0be004061318ec394eb64Kirill Grouchnikov        return mActivityTestRule.getActivity().mCoordinatorLayout;
80348cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki    }
80448cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki
80548cd60a8daad661611618f52a0dce00d5b2c64b9Yuichi Araki}
806