1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.wear.widget; 18 19import static org.mockito.Mockito.times; 20import static org.mockito.Mockito.verify; 21 22import static java.lang.Math.cos; 23import static java.lang.Math.sin; 24 25import android.os.SystemClock; 26import android.support.test.filters.MediumTest; 27import android.support.test.rule.ActivityTestRule; 28import android.support.test.runner.AndroidJUnit4; 29import android.view.MotionEvent; 30 31import androidx.wear.widget.util.WakeLockRule; 32 33import org.junit.Before; 34import org.junit.Rule; 35import org.junit.Test; 36import org.junit.runner.RunWith; 37import org.mockito.Mock; 38import org.mockito.MockitoAnnotations; 39 40@MediumTest 41@RunWith(AndroidJUnit4.class) 42public class ScrollManagerTest { 43 private static final int TEST_WIDTH = 400; 44 private static final int TEST_HEIGHT = 400; 45 private static final int STEP_COUNT = 300; 46 47 private static final int EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE = 36; 48 private static final int EXPECTED_SCROLLS_FOR_CIRCULAR_GESTURE = 199; 49 50 @Rule 51 public final WakeLockRule wakeLock = new WakeLockRule(); 52 53 @Rule 54 public final ActivityTestRule<WearableRecyclerViewTestActivity> mActivityRule = 55 new ActivityTestRule<>(WearableRecyclerViewTestActivity.class, true, true); 56 57 @Mock 58 WearableRecyclerView mMockWearableRecyclerView; 59 60 ScrollManager mScrollManagerUnderTest; 61 62 @Before 63 public void setUp() throws Throwable { 64 MockitoAnnotations.initMocks(this); 65 mScrollManagerUnderTest = new ScrollManager(); 66 mScrollManagerUnderTest.setRecyclerView(mMockWearableRecyclerView, TEST_WIDTH, TEST_HEIGHT); 67 } 68 69 @Test 70 public void testStraightUpScrollingGestureLeft() throws Throwable { 71 // Pretend to scroll in a straight line from center left to upper left 72 scroll(mScrollManagerUnderTest, 30, 30, 200, 150); 73 // The scroll manager should require the recycler view to scroll up and only up 74 verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE)) 75 .scrollBy(0, 1); 76 } 77 78 @Test 79 public void testStraightDownScrollingGestureLeft() throws Throwable { 80 // Pretend to scroll in a straight line upper left to center left 81 scroll(mScrollManagerUnderTest, 30, 30, 150, 200); 82 // The scroll manager should require the recycler view to scroll down and only down 83 verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE)) 84 .scrollBy(0, -1); 85 } 86 87 @Test 88 public void testStraightUpScrollingGestureRight() throws Throwable { 89 // Pretend to scroll in a straight line from center right to upper right 90 scroll(mScrollManagerUnderTest, 370, 370, 200, 150); 91 // The scroll manager should require the recycler view to scroll down and only down 92 verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE)) 93 .scrollBy(0, -1); 94 } 95 96 @Test 97 public void testStraightDownScrollingGestureRight() throws Throwable { 98 // Pretend to scroll in a straight line upper right to center right 99 scroll(mScrollManagerUnderTest, 370, 370, 150, 200); 100 // The scroll manager should require the recycler view to scroll up and only up 101 verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_STRAIGHT_GESTURE)) 102 .scrollBy(0, 1); 103 } 104 105 @Test 106 public void testCircularScrollingGestureLeft() throws Throwable { 107 // Pretend to scroll in an arch from center left to center right 108 scrollOnArch(mScrollManagerUnderTest, 30, 200, 180.0f); 109 // The scroll manager should never reverse the scroll direction and scroll up 110 verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_CIRCULAR_GESTURE)) 111 .scrollBy(0, 1); 112 } 113 114 @Test 115 public void testCircularScrollingGestureRight() throws Throwable { 116 // Pretend to scroll in an arch from center left to center right 117 scrollOnArch(mScrollManagerUnderTest, 370, 200, -180.0f); 118 // The scroll manager should never reverse the scroll direction and scroll down. 119 verify(mMockWearableRecyclerView, times(EXPECTED_SCROLLS_FOR_CIRCULAR_GESTURE)) 120 .scrollBy(0, -1); 121 } 122 123 private static void scroll(ScrollManager scrollManager, float fromX, float toX, float fromY, 124 float toY) { 125 long downTime = SystemClock.uptimeMillis(); 126 long eventTime = SystemClock.uptimeMillis(); 127 128 float y = fromY; 129 float x = fromX; 130 131 float yStep = (toY - fromY) / STEP_COUNT; 132 float xStep = (toX - fromX) / STEP_COUNT; 133 134 MotionEvent event = MotionEvent.obtain(downTime, eventTime, 135 MotionEvent.ACTION_DOWN, x, y, 0); 136 scrollManager.onTouchEvent(event); 137 for (int i = 0; i < STEP_COUNT; ++i) { 138 y += yStep; 139 x += xStep; 140 eventTime = SystemClock.uptimeMillis(); 141 event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); 142 scrollManager.onTouchEvent(event); 143 } 144 145 eventTime = SystemClock.uptimeMillis(); 146 event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); 147 scrollManager.onTouchEvent(event); 148 } 149 150 private static void scrollOnArch(ScrollManager scrollManager, float fromX, float fromY, 151 float deltaAngle) { 152 long downTime = SystemClock.uptimeMillis(); 153 long eventTime = SystemClock.uptimeMillis(); 154 155 float stepAngle = deltaAngle / STEP_COUNT; 156 double relativeX = fromX - (TEST_WIDTH / 2); 157 double relativeY = fromY - (TEST_HEIGHT / 2); 158 float radius = (float) Math.sqrt(relativeX * relativeX + relativeY * relativeY); 159 float angle = getAngle(fromX, fromY, TEST_WIDTH, TEST_HEIGHT); 160 161 float y = fromY; 162 float x = fromX; 163 164 MotionEvent event = MotionEvent.obtain(downTime, eventTime, 165 MotionEvent.ACTION_DOWN, x, y, 0); 166 scrollManager.onTouchEvent(event); 167 for (int i = 0; i < STEP_COUNT; ++i) { 168 angle += stepAngle; 169 x = getX(angle, radius, TEST_WIDTH); 170 y = getY(angle, radius, TEST_HEIGHT); 171 eventTime = SystemClock.uptimeMillis(); 172 event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); 173 scrollManager.onTouchEvent(event); 174 } 175 176 eventTime = SystemClock.uptimeMillis(); 177 event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); 178 scrollManager.onTouchEvent(event); 179 } 180 181 private static float getX(double angle, double radius, double viewWidth) { 182 double radianAngle = Math.toRadians(angle - 90); 183 double relativeX = cos(radianAngle) * radius; 184 return (float) (relativeX + (viewWidth / 2)); 185 } 186 187 private static float getY(double angle, double radius, double viewHeight) { 188 double radianAngle = Math.toRadians(angle - 90); 189 double relativeY = sin(radianAngle) * radius; 190 return (float) (relativeY + (viewHeight / 2)); 191 } 192 193 private static float getAngle(double x, double y, double viewWidth, double viewHeight) { 194 double relativeX = x - (viewWidth / 2); 195 double relativeY = y - (viewHeight / 2); 196 double rowAngle = Math.atan2(relativeX, relativeY); 197 double angle = -Math.toDegrees(rowAngle) - 180; 198 if (angle < 0) { 199 angle += 360; 200 } 201 return (float) angle; 202 } 203} 204