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 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