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 17 package com.android.server.accessibility.gestures; 18 19 import static org.mockito.ArgumentMatchers.argThat; 20 import static org.mockito.Mockito.mock; 21 import static org.mockito.Mockito.reset; 22 import static org.mockito.Mockito.verify; 23 import static org.mockito.Mockito.when; 24 25 import android.accessibilityservice.AccessibilityService; 26 import android.content.Context; 27 import android.graphics.Point; 28 import android.graphics.PointF; 29 import android.os.Handler; 30 import android.view.MotionEvent; 31 32 import androidx.test.InstrumentationRegistry; 33 34 import org.junit.Before; 35 import org.junit.Test; 36 37 import java.util.ArrayList; 38 39 /** 40 * Tests for GestureManifold 41 */ 42 public class GestureManifoldTest { 43 44 // Constants for testRecognizeGesturePath() 45 private static final PointF PATH_START = new PointF(300f, 300f); 46 private static final int PATH_STEP_PIXELS = 200; 47 private static final long PATH_STEP_MILLISEC = 100; 48 49 // Data used by all tests 50 private GestureManifold mManifold; 51 private TouchState mState; 52 private GestureManifold.Listener mResultListener; 53 54 @Before setUp()55 public void setUp() { 56 Context context = InstrumentationRegistry.getContext(); 57 // Construct a testable GestureManifold. 58 mResultListener = mock(GestureManifold.Listener.class); 59 mState = new TouchState(); 60 Handler handler = new Handler(context.getMainLooper()); 61 mManifold = new GestureManifold(context, mResultListener, mState, handler); 62 // Play the role of touch explorer in updating the shared state. 63 when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted()); 64 65 66 } 67 68 69 @Test testRecognizeGesturePath()70 public void testRecognizeGesturePath() { 71 final int d = 1000; // Length of each segment in the test gesture, in pixels. 72 73 testPath(p(-d, +0), AccessibilityService.GESTURE_SWIPE_LEFT); 74 testPath(p(+d, +0), AccessibilityService.GESTURE_SWIPE_RIGHT); 75 testPath(p(+0, -d), AccessibilityService.GESTURE_SWIPE_UP); 76 testPath(p(+0, +d), AccessibilityService.GESTURE_SWIPE_DOWN); 77 78 testPath(p(-d, +0), p((-d - d), +0), AccessibilityService.GESTURE_SWIPE_LEFT); 79 testPath(p(-d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT); 80 testPath(p(-d, +0), p(-d, -d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP); 81 testPath(p(-d, +0), p(-d, +d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN); 82 83 testPath(p(+d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT); 84 testPath(p(+d, +0), p((+d + d), +0), AccessibilityService.GESTURE_SWIPE_RIGHT); 85 testPath(p(+d, +0), p(+d, -d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP); 86 testPath(p(+d, +0), p(+d, +d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN); 87 88 testPath(p(+0, -d), p(-d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT); 89 testPath(p(+0, -d), p(+d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT); 90 testPath(p(+0, -d), p(+0, (-d - d)), AccessibilityService.GESTURE_SWIPE_UP); 91 testPath(p(+0, -d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN); 92 93 testPath(p(+0, +d), p(-d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT); 94 testPath(p(+0, +d), p(+d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT); 95 testPath(p(+0, +d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP); 96 testPath(p(+0, +d), p(+0, (+d + d)), AccessibilityService.GESTURE_SWIPE_DOWN); 97 } 98 99 /** Convenient short alias to make a Point. */ p(int x, int y)100 private static Point p(int x, int y) { 101 return new Point(x, y); 102 } 103 104 /** Test recognizing path from PATH_START to PATH_START+delta. */ testPath(Point delta, int gestureId)105 private void testPath(Point delta, int gestureId) { 106 ArrayList<PointF> path = new ArrayList<>(); 107 path.add(PATH_START); 108 109 PointF segmentEnd = new PointF(PATH_START.x + delta.x, PATH_START.y + delta.y); 110 fillPath(PATH_START, segmentEnd, path); 111 112 testPath(path, gestureId); 113 } 114 115 /** Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. */ testPath(Point delta1, Point delta2, int gestureId)116 private void testPath(Point delta1, Point delta2, int gestureId) { 117 ArrayList<PointF> path = new ArrayList<>(); 118 path.add(PATH_START); 119 120 PointF startPlusDelta1 = new PointF(PATH_START.x + delta1.x, PATH_START.y + delta1.y); 121 fillPath(PATH_START, startPlusDelta1, path); 122 123 PointF startPlusDelta2 = new PointF(PATH_START.x + delta2.x, PATH_START.y + delta2.y); 124 fillPath(startPlusDelta1, startPlusDelta2, path); 125 126 testPath(path, gestureId); 127 } 128 129 /** Fill in movement points from start to end, appending points to path. */ fillPath(PointF start, PointF end, ArrayList<PointF> path)130 private void fillPath(PointF start, PointF end, ArrayList<PointF> path) { 131 // Calculate number of path steps needed. 132 float deltaX = end.x - start.x; 133 float deltaY = end.y - start.y; 134 float distance = (float) Math.hypot(deltaX, deltaY); 135 float numSteps = distance / (float) PATH_STEP_PIXELS; 136 float stepX = (float) deltaX / numSteps; 137 float stepY = (float) deltaY / numSteps; 138 139 // For each path step from start (non-inclusive) to end ... add a motion point. 140 for (int step = 1; step < numSteps; ++step) { 141 path.add(new PointF( 142 (start.x + (stepX * (float) step)), 143 (start.y + (stepY * (float) step)))); 144 } 145 } 146 147 /** Test recognizing a path made of motion event points. */ testPath(ArrayList<PointF> path, int gestureId)148 private void testPath(ArrayList<PointF> path, int gestureId) { 149 // Clear last recognition result. 150 reset(mResultListener); 151 152 int policyFlags = 0; 153 long eventDownTimeMs = 0; 154 long eventTimeMs = eventDownTimeMs; 155 156 // For each path point... 157 for (int pointIndex = 0; pointIndex < path.size(); ++pointIndex) { 158 159 // Create motion event. 160 PointF point = path.get(pointIndex); 161 int action = MotionEvent.ACTION_MOVE; 162 if (pointIndex == 0) { 163 action = MotionEvent.ACTION_DOWN; 164 } else if (pointIndex == path.size() - 1) { 165 action = MotionEvent.ACTION_UP; 166 } 167 MotionEvent event = MotionEvent.obtain(eventDownTimeMs, eventTimeMs, action, 168 point.x, point.y, 0); 169 170 // Send event. 171 mState.onReceivedMotionEvent(event); 172 mManifold.onMotionEvent(event, event, policyFlags); 173 eventTimeMs += PATH_STEP_MILLISEC; 174 if (mState.isClear()) { 175 mState.startTouchInteracting(); 176 } 177 } 178 179 mState.clear(); 180 // Check that correct gesture was recognized. 181 verify(mResultListener).onGestureCompleted( 182 argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId)); 183 } 184 onGestureStarted()185 private boolean onGestureStarted() { 186 mState.startGestureDetecting(); 187 return false; 188 } 189 } 190