Home | History | Annotate | Download | only in accessibility
      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