15504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska/*
25504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * Copyright (C) 2017 The Android Open Source Project
35504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska *
45504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * Licensed under the Apache License, Version 2.0 (the "License");
55504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * you may not use this file except in compliance with the License.
65504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * You may obtain a copy of the License at
75504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska *
85504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska *      http://www.apache.org/licenses/LICENSE-2.0
95504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska *
105504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * Unless required by applicable law or agreed to in writing, software
115504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * distributed under the License is distributed on an "AS IS" BASIS,
125504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * See the License for the specific language governing permissions and
145504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska * limitations under the License.
155504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska */
165504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
179b028c55b13889bf75b5dd43b5b0f4051834ae1dAga Madurskapackage android.support.wear.widget;
185504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
195504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport static org.mockito.Mockito.times;
205504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport static org.mockito.Mockito.verify;
215504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
225504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport static java.lang.Math.cos;
235504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport static java.lang.Math.sin;
245504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
255504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport android.os.SystemClock;
265504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport android.support.test.filters.MediumTest;
275504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport android.support.test.rule.ActivityTestRule;
285504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport android.support.test.runner.AndroidJUnit4;
299b028c55b13889bf75b5dd43b5b0f4051834ae1dAga Madurskaimport android.support.wear.widget.util.WakeLockRule;
305504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport android.view.MotionEvent;
315504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
325504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport org.junit.Before;
335504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport org.junit.Rule;
345504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport org.junit.Test;
355504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport org.junit.runner.RunWith;
365504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport org.mockito.Mock;
375504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskaimport org.mockito.MockitoAnnotations;
385504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
395504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska@MediumTest
405504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska@RunWith(AndroidJUnit4.class)
415504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurskapublic class ScrollManagerTest {
425504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static final int TEST_WIDTH = 400;
435504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static final int TEST_HEIGHT = 400;
445504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static final int STEP_COUNT = 300;
455504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
465504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static final int EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE = 36;
475504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static final int EXPECTED_SCROLLS_FOR_CIRCULAR_GESTURE = 199;
485504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
495504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Rule
505504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public final WakeLockRule wakeLock = new WakeLockRule();
515504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
525504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Rule
535504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public final ActivityTestRule<WearableRecyclerViewTestActivity> mActivityRule =
545504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            new ActivityTestRule<>(WearableRecyclerViewTestActivity.class, true, true);
555504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
565504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Mock
575504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    WearableRecyclerView mMockWearableRecyclerView;
585504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
595504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    ScrollManager mScrollManagerUnderTest;
605504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
615504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Before
625504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public void setUp() throws Throwable {
635504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        MockitoAnnotations.initMocks(this);
645504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        mScrollManagerUnderTest = new ScrollManager();
655504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        mScrollManagerUnderTest.setRecyclerView(mMockWearableRecyclerView, TEST_WIDTH, TEST_HEIGHT);
665504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
675504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
685504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Test
695504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public void testStraightUpScrollingGestureLeft() throws Throwable {
705504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // Pretend to scroll in a straight line from center left to upper left
715504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scroll(mScrollManagerUnderTest, 30, 30, 200, 150);
725504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // The scroll manager should require the recycler view to scroll up and only up
735504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE))
745504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                .scrollBy(0, 1);
755504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
765504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
775504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Test
785504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public void testStraightDownScrollingGestureLeft() throws Throwable {
795504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // Pretend to scroll in a straight line upper left to center left
805504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scroll(mScrollManagerUnderTest, 30, 30, 150, 200);
815504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // The scroll manager should require the recycler view to scroll down and only down
825504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE))
835504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                .scrollBy(0, -1);
845504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
855504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
865504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Test
875504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public void testStraightUpScrollingGestureRight() throws Throwable {
885504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // Pretend to scroll in a straight line from center right to upper right
895504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scroll(mScrollManagerUnderTest, 370, 370, 200, 150);
905504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // The scroll manager should require the recycler view to scroll down and only down
915504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE))
925504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                .scrollBy(0, -1);
935504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
945504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
955504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Test
965504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public void testStraightDownScrollingGestureRight() throws Throwable {
975504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // Pretend to scroll in a straight line upper right to center right
985504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scroll(mScrollManagerUnderTest, 370, 370, 150, 200);
995504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // The scroll manager should require the recycler view to scroll up and only up
1005504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE))
1015504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                .scrollBy(0, 1);
1025504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
1035504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1045504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Test
1055504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public void testCircularScrollingGestureLeft() throws Throwable {
1065504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // Pretend to scroll in an arch from center left to center right
1075504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scrollOnArch(mScrollManagerUnderTest, 30, 200, 180.0f);
1085504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // The scroll manager should never reverse the scroll direction and scroll up
1095504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_CIRCULAR_GESTURE))
1105504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                .scrollBy(0, 1);
1115504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
1125504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1135504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    @Test
1145504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    public void testCircularScrollingGestureRight() throws Throwable {
1155504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // Pretend to scroll in an arch from center left to center right
1165504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scrollOnArch(mScrollManagerUnderTest, 370, 200, -180.0f);
1175504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        // The scroll manager should never reverse the scroll direction and scroll down.
1185504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_CIRCULAR_GESTURE))
1195504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                .scrollBy(0, -1);
1205504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
1215504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1225504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static void scroll(ScrollManager scrollManager, float fromX, float toX, float fromY,
1235504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            float toY) {
1245504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        long downTime = SystemClock.uptimeMillis();
1255504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        long eventTime = SystemClock.uptimeMillis();
1265504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1275504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float y = fromY;
1285504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float x = fromX;
1295504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1305504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float yStep = (toY - fromY) / STEP_COUNT;
1315504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float xStep = (toX - fromX) / STEP_COUNT;
1325504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1335504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
1345504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                MotionEvent.ACTION_DOWN, x, y, 0);
1355504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scrollManager.onTouchEvent(event);
1365504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        for (int i = 0; i < STEP_COUNT; ++i) {
1375504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            y += yStep;
1385504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            x += xStep;
1395504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            eventTime = SystemClock.uptimeMillis();
1405504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
1415504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            scrollManager.onTouchEvent(event);
1425504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        }
1435504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1445504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        eventTime = SystemClock.uptimeMillis();
1455504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
1465504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scrollManager.onTouchEvent(event);
1475504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
1485504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1495504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static void scrollOnArch(ScrollManager scrollManager, float fromX, float fromY,
1505504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            float deltaAngle) {
1515504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        long downTime = SystemClock.uptimeMillis();
1525504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        long eventTime = SystemClock.uptimeMillis();
1535504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1545504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float stepAngle = deltaAngle / STEP_COUNT;
1555504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double relativeX = fromX - (TEST_WIDTH / 2);
1565504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double relativeY = fromY - (TEST_HEIGHT / 2);
1575504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float radius = (float) Math.sqrt(relativeX * relativeX + relativeY * relativeY);
1585504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float angle = getAngle(fromX, fromY, TEST_WIDTH, TEST_HEIGHT);
1595504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1605504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float y = fromY;
1615504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        float x = fromX;
1625504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1635504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
1645504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska                MotionEvent.ACTION_DOWN, x, y, 0);
1655504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scrollManager.onTouchEvent(event);
1665504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        for (int i = 0; i < STEP_COUNT; ++i) {
1675504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            angle += stepAngle;
1685504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            x = getX(angle, radius, TEST_WIDTH);
1695504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            y = getY(angle, radius, TEST_HEIGHT);
1705504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            eventTime = SystemClock.uptimeMillis();
1715504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
1725504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            scrollManager.onTouchEvent(event);
1735504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        }
1745504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1755504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        eventTime = SystemClock.uptimeMillis();
1765504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
1775504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        scrollManager.onTouchEvent(event);
1785504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
1795504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1805504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static float getX(double angle, double radius, double viewWidth) {
1815504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double radianAngle = Math.toRadians(angle - 90);
1825504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double relativeX = cos(radianAngle) * radius;
1835504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        return (float) (relativeX + (viewWidth / 2));
1845504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
1855504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1865504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static float getY(double angle, double radius, double viewHeight) {
1875504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double radianAngle = Math.toRadians(angle - 90);
1885504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double relativeY = sin(radianAngle) * radius;
1895504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        return (float) (relativeY + (viewHeight / 2));
1905504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
1915504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska
1925504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    private static float getAngle(double x, double y, double viewWidth, double viewHeight) {
1935504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double relativeX = x - (viewWidth / 2);
1945504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double relativeY = y - (viewHeight / 2);
1955504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double rowAngle = Math.atan2(relativeX, relativeY);
1965504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        double angle = -Math.toDegrees(rowAngle) - 180;
1975504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        if (angle < 0) {
1985504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska            angle += 360;
1995504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        }
2005504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska        return (float) angle;
2015504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska    }
2025504220f38bb0552e1d8def09fb1b9a118264b45Aga Madurska}
203