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; 18 19 import static org.mockito.Mockito.mock; 20 import static org.mockito.Mockito.reset; 21 import static org.mockito.Mockito.when; 22 import static org.mockito.Mockito.verify; 23 24 import android.accessibilityservice.AccessibilityService; 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.graphics.Point; 28 import android.graphics.PointF; 29 import android.os.Looper; 30 import android.util.DisplayMetrics; 31 import android.view.GestureDetector; 32 import android.view.MotionEvent; 33 import java.util.ArrayList; 34 import org.junit.Before; 35 import org.junit.BeforeClass; 36 import org.junit.Test; 37 38 39 /** 40 * Tests for AccessibilityGestureDetector 41 */ 42 public class AccessibilityGestureDetectorTest { 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 /** 50 * AccessibilitGestureDetector that can mock double-tap detector. 51 */ 52 private class AccessibilityGestureDetectorTestable extends AccessibilityGestureDetector { 53 public AccessibilityGestureDetectorTestable(Context context, Listener listener) { 54 super(context, listener); 55 } 56 57 protected void setDoubleTapDetector(GestureDetector gestureDetector) { 58 mGestureDetector = gestureDetector; 59 mGestureDetector.setOnDoubleTapListener(this); 60 } 61 } 62 63 64 // Data used by all tests 65 private AccessibilityGestureDetectorTestable mDetector; 66 private AccessibilityGestureDetector.Listener mResultListener; 67 68 69 @BeforeClass 70 public static void oneTimeInitialization() { 71 if (Looper.myLooper() == null) { 72 Looper.prepare(); 73 } 74 } 75 76 @Before 77 public void setUp() { 78 // Construct a mock Context. 79 DisplayMetrics displayMetricsMock = mock(DisplayMetrics.class); 80 displayMetricsMock.xdpi = 500; 81 displayMetricsMock.ydpi = 500; 82 Resources mockResources = mock(Resources.class); 83 when(mockResources.getDisplayMetrics()).thenReturn(displayMetricsMock); 84 Context contextMock = mock(Context.class); 85 when(contextMock.getMainLooper()).thenReturn(Looper.myLooper()); 86 when(contextMock.getResources()).thenReturn(mockResources); 87 88 // Construct a testable AccessibilityGestureDetector. 89 mResultListener = mock(AccessibilityGestureDetector.Listener.class); 90 mDetector = new AccessibilityGestureDetectorTestable(contextMock, mResultListener); 91 GestureDetector doubleTapDetectorMock = mock(GestureDetector.class); 92 mDetector.setDoubleTapDetector(doubleTapDetectorMock); 93 } 94 95 96 @Test 97 public void testRecognizeGesturePath() { 98 final int d = 1000; // Length of each segment in the test gesture, in pixels. 99 100 testPath(p(-d, +0), AccessibilityService.GESTURE_SWIPE_LEFT); 101 testPath(p(+d, +0), AccessibilityService.GESTURE_SWIPE_RIGHT); 102 testPath(p(+0, -d), AccessibilityService.GESTURE_SWIPE_UP); 103 testPath(p(+0, +d), AccessibilityService.GESTURE_SWIPE_DOWN); 104 105 testPath(p(-d, +0), p((-d - d), +0), AccessibilityService.GESTURE_SWIPE_LEFT); 106 testPath(p(-d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT); 107 testPath(p(-d, +0), p(-d, -d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP); 108 testPath(p(-d, +0), p(-d, +d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN); 109 110 testPath(p(+d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT); 111 testPath(p(+d, +0), p((+d + d), +0), AccessibilityService.GESTURE_SWIPE_RIGHT); 112 testPath(p(+d, +0), p(+d, -d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP); 113 testPath(p(+d, +0), p(+d, +d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN); 114 115 testPath(p(+0, -d), p(-d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT); 116 testPath(p(+0, -d), p(+d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT); 117 testPath(p(+0, -d), p(+0, (-d - d)), AccessibilityService.GESTURE_SWIPE_UP); 118 testPath(p(+0, -d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN); 119 120 testPath(p(+0, +d), p(-d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT); 121 testPath(p(+0, +d), p(+d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT); 122 testPath(p(+0, +d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP); 123 testPath(p(+0, +d), p(+0, (+d + d)), AccessibilityService.GESTURE_SWIPE_DOWN); 124 } 125 126 /** Convenient short alias to make a Point. */ 127 private static Point p(int x, int y) { 128 return new Point(x, y); 129 } 130 131 /** Test recognizing path from PATH_START to PATH_START+delta. */ 132 private void testPath(Point delta, int gestureId) { 133 ArrayList<PointF> path = new ArrayList<>(); 134 path.add(PATH_START); 135 136 PointF segmentEnd = new PointF(PATH_START.x + delta.x, PATH_START.y + delta.y); 137 fillPath(PATH_START, segmentEnd, path); 138 139 testPath(path, gestureId); 140 } 141 142 /** Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. */ 143 private void testPath(Point delta1, Point delta2, int gestureId) { 144 ArrayList<PointF> path = new ArrayList<>(); 145 path.add(PATH_START); 146 147 PointF startPlusDelta1 = new PointF(PATH_START.x + delta1.x, PATH_START.y + delta1.y); 148 fillPath(PATH_START, startPlusDelta1, path); 149 150 PointF startPlusDelta2 = new PointF(PATH_START.x + delta2.x, PATH_START.y + delta2.y); 151 fillPath(startPlusDelta1, startPlusDelta2, path); 152 153 testPath(path, gestureId); 154 } 155 156 /** Fill in movement points from start to end, appending points to path. */ 157 private void fillPath(PointF start, PointF end, ArrayList<PointF> path) { 158 // Calculate number of path steps needed. 159 float deltaX = end.x - start.x; 160 float deltaY = end.y - start.y; 161 float distance = (float) Math.hypot(deltaX, deltaY); 162 float numSteps = distance / (float) PATH_STEP_PIXELS; 163 float stepX = (float) deltaX / numSteps; 164 float stepY = (float) deltaY / numSteps; 165 166 // For each path step from start (non-inclusive) to end ... add a motion point. 167 for (int step = 1; step < numSteps; ++step) { 168 path.add(new PointF( 169 (start.x + (stepX * (float) step)), 170 (start.y + (stepY * (float) step)))); 171 } 172 } 173 174 /** Test recognizing a path made of motion event points. */ 175 private void testPath(ArrayList<PointF> path, int gestureId) { 176 // Clear last recognition result. 177 reset(mResultListener); 178 179 int policyFlags = 0; 180 long eventDownTimeMs = 0; 181 long eventTimeMs = eventDownTimeMs; 182 183 // For each path point... 184 for (int pointIndex = 0; pointIndex < path.size(); ++pointIndex) { 185 186 // Create motion event. 187 PointF point = path.get(pointIndex); 188 int action = MotionEvent.ACTION_MOVE; 189 if (pointIndex == 0) { 190 action = MotionEvent.ACTION_DOWN; 191 } else if (pointIndex == path.size() - 1) { 192 action = MotionEvent.ACTION_UP; 193 } 194 MotionEvent event = MotionEvent.obtain(eventDownTimeMs, eventTimeMs, action, 195 point.x, point.y, 0); 196 197 // Send event. 198 mDetector.onMotionEvent(event, policyFlags); 199 eventTimeMs += PATH_STEP_MILLISEC; 200 } 201 202 // Check that correct gesture was recognized. 203 verify(mResultListener).onGestureCompleted(gestureId); 204 } 205 } 206