| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.accessibility.gestures; |
| |
| import static org.mockito.ArgumentMatchers.argThat; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.accessibilityservice.AccessibilityService; |
| import android.content.Context; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.os.Handler; |
| import android.view.MotionEvent; |
| |
| import androidx.test.InstrumentationRegistry; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Tests for GestureManifold |
| */ |
| public class GestureManifoldTest { |
| |
| // Constants for testRecognizeGesturePath() |
| private static final PointF PATH_START = new PointF(300f, 300f); |
| private static final int PATH_STEP_PIXELS = 200; |
| private static final long PATH_STEP_MILLISEC = 100; |
| |
| // Data used by all tests |
| private GestureManifold mManifold; |
| private TouchState mState; |
| private GestureManifold.Listener mResultListener; |
| |
| @Before |
| public void setUp() { |
| Context context = InstrumentationRegistry.getContext(); |
| // Construct a testable GestureManifold. |
| mResultListener = mock(GestureManifold.Listener.class); |
| mState = new TouchState(); |
| Handler handler = new Handler(context.getMainLooper()); |
| mManifold = new GestureManifold(context, mResultListener, mState, handler); |
| // Play the role of touch explorer in updating the shared state. |
| when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted()); |
| |
| |
| } |
| |
| |
| @Test |
| public void testRecognizeGesturePath() { |
| final int d = 1000; // Length of each segment in the test gesture, in pixels. |
| |
| testPath(p(-d, +0), AccessibilityService.GESTURE_SWIPE_LEFT); |
| testPath(p(+d, +0), AccessibilityService.GESTURE_SWIPE_RIGHT); |
| testPath(p(+0, -d), AccessibilityService.GESTURE_SWIPE_UP); |
| testPath(p(+0, +d), AccessibilityService.GESTURE_SWIPE_DOWN); |
| |
| testPath(p(-d, +0), p((-d - d), +0), AccessibilityService.GESTURE_SWIPE_LEFT); |
| testPath(p(-d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT); |
| testPath(p(-d, +0), p(-d, -d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP); |
| testPath(p(-d, +0), p(-d, +d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN); |
| |
| testPath(p(+d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT); |
| testPath(p(+d, +0), p((+d + d), +0), AccessibilityService.GESTURE_SWIPE_RIGHT); |
| testPath(p(+d, +0), p(+d, -d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP); |
| testPath(p(+d, +0), p(+d, +d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN); |
| |
| testPath(p(+0, -d), p(-d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT); |
| testPath(p(+0, -d), p(+d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT); |
| testPath(p(+0, -d), p(+0, (-d - d)), AccessibilityService.GESTURE_SWIPE_UP); |
| testPath(p(+0, -d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN); |
| |
| testPath(p(+0, +d), p(-d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT); |
| testPath(p(+0, +d), p(+d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT); |
| testPath(p(+0, +d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP); |
| testPath(p(+0, +d), p(+0, (+d + d)), AccessibilityService.GESTURE_SWIPE_DOWN); |
| } |
| |
| /** Convenient short alias to make a Point. */ |
| private static Point p(int x, int y) { |
| return new Point(x, y); |
| } |
| |
| /** Test recognizing path from PATH_START to PATH_START+delta. */ |
| private void testPath(Point delta, int gestureId) { |
| ArrayList<PointF> path = new ArrayList<>(); |
| path.add(PATH_START); |
| |
| PointF segmentEnd = new PointF(PATH_START.x + delta.x, PATH_START.y + delta.y); |
| fillPath(PATH_START, segmentEnd, path); |
| |
| testPath(path, gestureId); |
| } |
| |
| /** Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. */ |
| private void testPath(Point delta1, Point delta2, int gestureId) { |
| ArrayList<PointF> path = new ArrayList<>(); |
| path.add(PATH_START); |
| |
| PointF startPlusDelta1 = new PointF(PATH_START.x + delta1.x, PATH_START.y + delta1.y); |
| fillPath(PATH_START, startPlusDelta1, path); |
| |
| PointF startPlusDelta2 = new PointF(PATH_START.x + delta2.x, PATH_START.y + delta2.y); |
| fillPath(startPlusDelta1, startPlusDelta2, path); |
| |
| testPath(path, gestureId); |
| } |
| |
| /** Fill in movement points from start to end, appending points to path. */ |
| private void fillPath(PointF start, PointF end, ArrayList<PointF> path) { |
| // Calculate number of path steps needed. |
| float deltaX = end.x - start.x; |
| float deltaY = end.y - start.y; |
| float distance = (float) Math.hypot(deltaX, deltaY); |
| float numSteps = distance / (float) PATH_STEP_PIXELS; |
| float stepX = (float) deltaX / numSteps; |
| float stepY = (float) deltaY / numSteps; |
| |
| // For each path step from start (non-inclusive) to end ... add a motion point. |
| for (int step = 1; step < numSteps; ++step) { |
| path.add(new PointF( |
| (start.x + (stepX * (float) step)), |
| (start.y + (stepY * (float) step)))); |
| } |
| } |
| |
| /** Test recognizing a path made of motion event points. */ |
| private void testPath(ArrayList<PointF> path, int gestureId) { |
| // Clear last recognition result. |
| reset(mResultListener); |
| |
| int policyFlags = 0; |
| long eventDownTimeMs = 0; |
| long eventTimeMs = eventDownTimeMs; |
| |
| // For each path point... |
| for (int pointIndex = 0; pointIndex < path.size(); ++pointIndex) { |
| |
| // Create motion event. |
| PointF point = path.get(pointIndex); |
| int action = MotionEvent.ACTION_MOVE; |
| if (pointIndex == 0) { |
| action = MotionEvent.ACTION_DOWN; |
| } else if (pointIndex == path.size() - 1) { |
| action = MotionEvent.ACTION_UP; |
| } |
| MotionEvent event = MotionEvent.obtain(eventDownTimeMs, eventTimeMs, action, |
| point.x, point.y, 0); |
| |
| // Send event. |
| mState.onReceivedMotionEvent(event); |
| mManifold.onMotionEvent(event, event, policyFlags); |
| eventTimeMs += PATH_STEP_MILLISEC; |
| if (mState.isClear()) { |
| mState.startTouchInteracting(); |
| } |
| } |
| |
| mState.clear(); |
| // Check that correct gesture was recognized. |
| verify(mResultListener).onGestureCompleted( |
| argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId)); |
| } |
| |
| private boolean onGestureStarted() { |
| mState.startGestureDetecting(); |
| return false; |
| } |
| } |