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 
17 package com.android.server.accessibility;
18 
19 import static org.hamcrest.CoreMatchers.allOf;
20 import static org.hamcrest.CoreMatchers.everyItem;
21 import static org.hamcrest.MatcherAssert.assertThat;
22 
23 import android.accessibilityservice.GestureDescription;
24 import android.accessibilityservice.GestureDescription.GestureStep;
25 import android.accessibilityservice.GestureDescription.MotionEventGenerator;
26 import android.accessibilityservice.GestureDescription.StrokeDescription;
27 import android.graphics.Path;
28 import android.graphics.PointF;
29 import org.hamcrest.Description;
30 import org.hamcrest.Matcher;
31 import org.hamcrest.TypeSafeMatcher;
32 import org.junit.Test;
33 
34 import java.util.List;
35 
36 import static junit.framework.TestCase.assertEquals;
37 
38 /**
39  * Tests for GestureDescription
40  */
41 public class GestureDescriptionTest {
42     @Test
testGestureShorterThanSampleRate_producesStartAndEnd()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
testSwipe_shouldContainEvenlySpacedPoints()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
testSwipeWithNonIntegerValues_shouldRound()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
testPathsWithOverlappingTiming_produceCorrectSteps()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
testMaxTouchpoints_shouldHaveValidCoords()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
testGetGestureSteps_touchPointsHaveStrokeId()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
testGetGestureSteps_continuedStroke_hasNoEndPoint()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
testGetGestureSteps_continuingStroke_hasNoStartPointAndHasContinuedId()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 
createSwipe( float startX, float startY, float endX, float endY, long duration)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 
createSwipeStroke( float startX, float startY, float endX, float endY, long startTime, long endTime)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 
numTouchPointsIs(final int numTouchPoints)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