GestureDescriptionTest.java revision be2922ff34424cfb996d895cde0cb31c724d09dc
1/*
2 * Copyright (C) 2016 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 com.android.server.accessibility;
18
19import static org.hamcrest.CoreMatchers.allOf;
20import static org.hamcrest.CoreMatchers.everyItem;
21import static org.hamcrest.MatcherAssert.assertThat;
22
23import android.accessibilityservice.GestureDescription;
24import android.accessibilityservice.GestureDescription.GestureStep;
25import android.accessibilityservice.GestureDescription.MotionEventGenerator;
26import android.accessibilityservice.GestureDescription.StrokeDescription;
27import android.graphics.Path;
28import android.graphics.PointF;
29import org.hamcrest.Description;
30import org.hamcrest.Matcher;
31import org.hamcrest.TypeSafeMatcher;
32import org.junit.Test;
33
34import java.util.List;
35
36import static junit.framework.TestCase.assertEquals;
37
38/**
39 * Tests for GestureDescription
40 */
41public class GestureDescriptionTest {
42    @Test
43    public void testGestureShorterThanSampleRate_producesStartAndEnd() {
44        PointF click = new PointF(10, 20);
45        Path clickPath = new Path();
46        clickPath.moveTo(click.x, click.y);
47        StrokeDescription clickStroke = new StrokeDescription(clickPath, 0, 10);
48        GestureDescription.Builder clickBuilder = new GestureDescription.Builder();
49        clickBuilder.addStroke(clickStroke);
50        GestureDescription clickGesture = clickBuilder.build();
51
52        List<GestureStep> clickGestureSteps = MotionEventGenerator
53                .getGestureStepsFromGestureDescription(clickGesture, 100);
54
55        assertEquals(2, clickGestureSteps.size());
56        assertThat(clickGestureSteps.get(0), allOf(numTouchPointsIs(1), numStartsOfStroke(1),
57                numEndsOfStroke(0), hasPoint(click)));
58        assertThat(clickGestureSteps.get(1), allOf(numTouchPointsIs(1), numStartsOfStroke(0),
59                numEndsOfStroke(1), hasPoint(click)));
60    }
61
62    @Test
63    public void testSwipe_shouldContainEvenlySpacedPoints() {
64        int samplePeriod = 10;
65        int numSamples = 5;
66        float stepX = 2;
67        float stepY = 3;
68        PointF start = new PointF(10, 20);
69        PointF end = new PointF(10 + numSamples * stepX, 20 + numSamples * stepY);
70
71        GestureDescription swipe =
72                createSwipe(start.x, start.y, end.x, end.y, numSamples * samplePeriod);
73        List<GestureStep> swipeGestureSteps = MotionEventGenerator
74                .getGestureStepsFromGestureDescription(swipe, samplePeriod);
75        assertEquals(numSamples + 1, swipeGestureSteps.size());
76
77        assertThat(swipeGestureSteps.get(0), allOf(numTouchPointsIs(1), numStartsOfStroke(1),
78                numEndsOfStroke(0), hasPoint(start)));
79        assertThat(swipeGestureSteps.get(numSamples), allOf(numTouchPointsIs(1),
80                numStartsOfStroke(0), numEndsOfStroke(1), hasPoint(end)));
81
82        for (int i = 1; i < numSamples; ++i) {
83            PointF interpPoint = new PointF(start.x + stepX * i, start.y + stepY * i);
84            assertThat(swipeGestureSteps.get(i), allOf(numTouchPointsIs(1),
85                    numStartsOfStroke(0), numEndsOfStroke(0), hasPoint(interpPoint)));
86        }
87    }
88
89    @Test
90    public void testSwipeWithNonIntegerValues_shouldRound() {
91        int strokeTime = 10;
92
93        GestureDescription swipe = createSwipe(10.1f, 20.6f, 11.9f, 22.1f, strokeTime);
94        List<GestureStep> swipeGestureSteps = MotionEventGenerator
95                .getGestureStepsFromGestureDescription(swipe, strokeTime);
96        assertEquals(2, swipeGestureSteps.size());
97        assertThat(swipeGestureSteps.get(0), hasPoint(new PointF(10, 21)));
98        assertThat(swipeGestureSteps.get(1), hasPoint(new PointF(12, 22)));
99    }
100
101    @Test
102    public void testPathsWithOverlappingTiming_produceCorrectSteps() {
103        // There are 4 paths
104        // 0: an L-shaped path that starts first
105        // 1: a swipe that starts in the middle of the L-shaped path and ends when the L ends
106        // 2: a swipe that starts at the same time as #1 but extends past the end of the L
107        // 3: a swipe that starts when #3 ends
108        PointF path0Start = new PointF(100, 150);
109        PointF path0Turn = new PointF(100, 200);
110        PointF path0End = new PointF(250, 200);
111        int path0StartTime = 0;
112        int path0EndTime = 100;
113        int path0Duration = path0EndTime - path0StartTime;
114        Path path0 = new Path();
115        path0.moveTo(path0Start.x, path0Start.y);
116        path0.lineTo(path0Turn.x, path0Turn.y);
117        path0.lineTo(path0End.x, path0End.y);
118        StrokeDescription path0Stroke = new StrokeDescription(path0, path0StartTime, path0Duration);
119
120        PointF path1Start = new PointF(300, 350);
121        PointF path1End = new PointF(300, 400);
122        int path1StartTime = 50;
123        int path1EndTime = path0EndTime;
124        StrokeDescription path1Stroke = createSwipeStroke(
125                path1Start.x, path1Start.y, path1End.x, path1End.y, path1StartTime, path1EndTime);
126
127        PointF path2Start = new PointF(400, 450);
128        PointF path2End = new PointF(400, 500);
129        int path2StartTime = 50;
130        int path2EndTime = 150;
131        StrokeDescription path2Stroke = createSwipeStroke(
132                path2Start.x, path2Start.y, path2End.x, path2End.y, path2StartTime, path2EndTime);
133
134        PointF path3Start = new PointF(500, 550);
135        PointF path3End = new PointF(500, 600);
136        int path3StartTime = path2EndTime;
137        int path3EndTime = 200;
138        StrokeDescription path3Stroke = createSwipeStroke(
139                path3Start.x, path3Start.y, path3End.x, path3End.y, path3StartTime, path3EndTime);
140
141        int deltaT = 12; // Force samples to happen on extra boundaries
142        GestureDescription.Builder builder = new GestureDescription.Builder();
143        builder.addStroke(path0Stroke);
144        builder.addStroke(path1Stroke);
145        builder.addStroke(path2Stroke);
146        builder.addStroke(path3Stroke);
147        List<GestureStep> steps = MotionEventGenerator
148                .getGestureStepsFromGestureDescription(builder.build(), deltaT);
149
150        long start = 0;
151        assertThat(steps.get(0), allOf(numStartsOfStroke(1), numEndsOfStroke(0), isAtTime(start),
152                numTouchPointsIs(1), hasPoint(path0Start)));
153        assertThat(steps.get(1), allOf(numTouchPointsIs(1), noStartsOrEnds(),
154                isAtTime(start + deltaT)));
155        assertThat(steps.get(2), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 2)));
156        assertThat(steps.get(3), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 3)));
157        assertThat(steps.get(4), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 4)));
158
159        assertThat(steps.get(5), allOf(numTouchPointsIs(3), numStartsOfStroke(2),
160                numEndsOfStroke(0), isAtTime(path1StartTime), hasPoint(path1Start),
161                hasPoint(path2Start)));
162
163        start = path1StartTime;
164        assertThat(steps.get(6), allOf(numTouchPointsIs(3), isAtTime(start + deltaT * 1)));
165        assertThat(steps.get(7), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 2)));
166        assertThat(steps.get(8), allOf(numTouchPointsIs(3), isAtTime(start + deltaT * 3)));
167        assertThat(steps.get(9), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 4)));
168
169        assertThat(steps.get(10), allOf(numTouchPointsIs(3), numStartsOfStroke(0),
170                numEndsOfStroke(2), isAtTime(path0EndTime), hasPoint(path0End),
171                hasPoint(path1End)));
172
173        start = path0EndTime;
174        assertThat(steps.get(11), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 1)));
175        assertThat(steps.get(12), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 2)));
176        assertThat(steps.get(13), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 3)));
177        assertThat(steps.get(14), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 4)));
178
179        assertThat(steps.get(15), allOf(numTouchPointsIs(2), numStartsOfStroke(1),
180                numEndsOfStroke(1), isAtTime(path2EndTime), hasPoint(path2End),
181                hasPoint(path3Start)));
182
183        start = path2EndTime;
184        assertThat(steps.get(16), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 1)));
185        assertThat(steps.get(17), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 2)));
186        assertThat(steps.get(18), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 3)));
187        assertThat(steps.get(19), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 4)));
188
189        assertThat(steps.get(20), allOf(numTouchPointsIs(1), numStartsOfStroke(0),
190                numEndsOfStroke(1), isAtTime(path3EndTime), hasPoint(path3End)));
191    }
192
193    @Test
194    public void testMaxTouchpoints_shouldHaveValidCoords() {
195        GestureDescription.Builder maxPointBuilder = new GestureDescription.Builder();
196        PointF baseStartPoint = new PointF(100, 100);
197        PointF baseEndPoint = new PointF(100, 200);
198        int xStep = 10;
199        int samplePeriod = 15;
200        int numSamples = 2;
201        int numPoints = GestureDescription.getMaxStrokeCount();
202        for (int i = 0; i < numPoints; i++) {
203            Path path = new Path();
204            path.moveTo(baseStartPoint.x + i * xStep, baseStartPoint.y);
205            path.lineTo(baseEndPoint.x + i * xStep, baseEndPoint.y);
206            maxPointBuilder.addStroke(new StrokeDescription(path, 0, samplePeriod * numSamples));
207        }
208
209        List<GestureStep> steps = MotionEventGenerator
210                .getGestureStepsFromGestureDescription(maxPointBuilder.build(), samplePeriod);
211        assertEquals(3, steps.size());
212
213        assertThat(steps.get(0), allOf(numTouchPointsIs(numPoints), numStartsOfStroke(numPoints),
214                numEndsOfStroke(0), isAtTime(0)));
215        assertThat(steps.get(1), allOf(numTouchPointsIs(numPoints), numStartsOfStroke(0),
216                numEndsOfStroke(0), isAtTime(samplePeriod)));
217        assertThat(steps.get(2), allOf(numTouchPointsIs(numPoints), numStartsOfStroke(0),
218                numEndsOfStroke(numPoints), isAtTime(samplePeriod * 2)));
219
220        PointF baseMidPoint = new PointF((baseStartPoint.x + baseEndPoint.x) / 2,
221                (baseStartPoint.y + baseEndPoint.y) / 2);
222        for (int i = 0; i < numPoints; i++) {
223            assertThat(steps.get(0),
224                    hasPoint(new PointF(baseStartPoint.x + i * xStep, baseStartPoint.y)));
225            assertThat(steps.get(1),
226                    hasPoint(new PointF(baseMidPoint.x + i * xStep, baseMidPoint.y)));
227            assertThat(steps.get(2),
228                    hasPoint(new PointF(baseEndPoint.x + i * xStep, baseEndPoint.y)));
229        }
230    }
231
232    @Test
233    public void testGetGestureSteps_touchPointsHaveStrokeId() {
234        StrokeDescription swipeStroke = createSwipeStroke(10, 20, 30, 40, 0, 100);
235        GestureDescription swipe = new GestureDescription.Builder().addStroke(swipeStroke).build();
236        List<GestureStep> swipeGestureSteps = MotionEventGenerator
237                .getGestureStepsFromGestureDescription(swipe, 10);
238
239        assertThat(swipeGestureSteps, everyItem(hasStrokeId(swipeStroke.getId())));
240    }
241
242    @Test
243    public void testGetGestureSteps_continuedStroke_hasNoEndPoint() {
244        Path swipePath = new Path();
245        swipePath.moveTo(10, 20);
246        swipePath.lineTo(30, 40);
247        StrokeDescription stroke1 =
248                new StrokeDescription(swipePath, 0, 100, true);
249        GestureDescription gesture = new GestureDescription.Builder().addStroke(stroke1).build();
250        List<GestureStep> steps = MotionEventGenerator
251                .getGestureStepsFromGestureDescription(gesture, 10);
252
253        assertThat(steps, everyItem(numEndsOfStroke(0)));
254    }
255
256    @Test
257    public void testGetGestureSteps_continuingStroke_hasNoStartPointAndHasContinuedId() {
258        Path swipePath = new Path();
259        swipePath.moveTo(10, 20);
260        swipePath.lineTo(30, 40);
261        StrokeDescription stroke1 =
262                new StrokeDescription(swipePath, 0, 100, true);
263        StrokeDescription stroke2 = stroke1.continueStroke(swipePath, 0, 100, false);
264        GestureDescription gesture = new GestureDescription.Builder().addStroke(stroke2).build();
265        List<GestureStep> steps = MotionEventGenerator
266                .getGestureStepsFromGestureDescription(gesture, 10);
267
268        assertThat(steps, everyItem(
269                allOf(continuesStrokeId(stroke1.getId()), numStartsOfStroke(0))));
270    }
271
272    private GestureDescription createSwipe(
273            float startX, float startY, float endX, float endY, long duration) {
274        GestureDescription.Builder swipeBuilder = new GestureDescription.Builder();
275        swipeBuilder.addStroke(createSwipeStroke(startX, startY, endX, endY, 0, duration));
276        return swipeBuilder.build();
277    }
278
279    private StrokeDescription createSwipeStroke(
280            float startX, float startY, float endX, float endY, long startTime, long endTime) {
281        Path swipePath = new Path();
282        swipePath.moveTo(startX, startY);
283        swipePath.lineTo(endX, endY);
284        StrokeDescription swipeStroke =
285                new StrokeDescription(swipePath, startTime, endTime - startTime);
286        return swipeStroke;
287    }
288
289    Matcher<GestureStep> numTouchPointsIs(final int numTouchPoints) {
290        return new TypeSafeMatcher<GestureStep>() {
291            @Override
292            protected boolean matchesSafely(GestureStep gestureStep) {
293                return gestureStep.numTouchPoints == numTouchPoints;
294            }
295
296            @Override
297            public void describeTo(Description description) {
298                description.appendText("Has " + numTouchPoints + " touch point(s)");
299            }
300        };
301    }
302
303    Matcher<GestureStep> numStartsOfStroke(final int numStarts) {
304        return new TypeSafeMatcher<GestureStep>() {
305            @Override
306            protected boolean matchesSafely(GestureStep gestureStep) {
307                int numStartsFound = 0;
308                for (int i = 0; i < gestureStep.numTouchPoints; i++) {
309                    if (gestureStep.touchPoints[i].mIsStartOfPath) {
310                        numStartsFound++;
311                    }
312                }
313                return numStartsFound == numStarts;
314            }
315
316            @Override
317            public void describeTo(Description description) {
318                description.appendText("Starts " + numStarts + " stroke(s)");
319            }
320        };
321    }
322
323    Matcher<GestureStep> numEndsOfStroke(final int numEnds) {
324        return new TypeSafeMatcher<GestureStep>() {
325            @Override
326            protected boolean matchesSafely(GestureStep gestureStep) {
327                int numEndsFound = 0;
328                for (int i = 0; i < gestureStep.numTouchPoints; i++) {
329                    if (gestureStep.touchPoints[i].mIsEndOfPath) {
330                        numEndsFound++;
331                    }
332                }
333                return numEndsFound == numEnds;
334            }
335
336            @Override
337            public void describeTo(Description description) {
338                description.appendText("Ends " + numEnds + " stroke(s)");
339            }
340        };
341    }
342
343    Matcher<GestureStep> hasPoint(final PointF point) {
344        return new TypeSafeMatcher<GestureStep>() {
345            @Override
346            protected boolean matchesSafely(GestureStep gestureStep) {
347                for (int i = 0; i < gestureStep.numTouchPoints; i++) {
348                    if ((gestureStep.touchPoints[i].mX == point.x)
349                            && (gestureStep.touchPoints[i].mY == point.y)) {
350                        return true;
351                    }
352                }
353                return false;
354            }
355
356            @Override
357            public void describeTo(Description description) {
358                description.appendText("Has at least one point at " + point);
359            }
360        };
361    }
362
363    Matcher<GestureStep> hasStrokeId(final int strokeId) {
364        return new TypeSafeMatcher<GestureStep>() {
365            @Override
366            protected boolean matchesSafely(GestureStep gestureStep) {
367                for (int i = 0; i < gestureStep.numTouchPoints; i++) {
368                    if (gestureStep.touchPoints[i].mStrokeId == strokeId) {
369                        return true;
370                    }
371                }
372                return false;
373            }
374
375            @Override
376            public void describeTo(Description description) {
377                description.appendText("Has at least one point with stroke id " + strokeId);
378            }
379        };
380    }
381
382    Matcher<GestureStep> continuesStrokeId(final int strokeId) {
383        return new TypeSafeMatcher<GestureStep>() {
384            @Override
385            protected boolean matchesSafely(GestureStep gestureStep) {
386                for (int i = 0; i < gestureStep.numTouchPoints; i++) {
387                    if (gestureStep.touchPoints[i].mContinuedStrokeId == strokeId) {
388                        return true;
389                    }
390                }
391                return false;
392            }
393
394            @Override
395            public void describeTo(Description description) {
396                description.appendText("Continues stroke id " + strokeId);
397            }
398        };
399    }
400
401    Matcher<GestureStep> isAtTime(final long time) {
402        return new TypeSafeMatcher<GestureStep>() {
403            @Override
404            protected boolean matchesSafely(GestureStep gestureStep) {
405                return gestureStep.timeSinceGestureStart == time;
406            }
407
408            @Override
409            public void describeTo(Description description) {
410                description.appendText("Is at time " + time);
411            }
412        };
413    }
414
415    Matcher<GestureStep> noStartsOrEnds() {
416        return allOf(numStartsOfStroke(0), numEndsOfStroke(0));
417    }
418}
419