Home | History | Annotate | Download | only in cts
      1 /**
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
     11  * express or implied. See the License for the specific language governing permissions and
     12  * limitations under the License.
     13  */
     14 
     15 package android.accessibilityservice.cts;
     16 
     17 import android.accessibilityservice.AccessibilityService;
     18 import android.accessibilityservice.GestureDescription;
     19 import android.content.pm.PackageManager;
     20 import android.content.res.Resources;
     21 import android.graphics.Matrix;
     22 import android.graphics.Path;
     23 import android.graphics.Rect;
     24 import android.os.Bundle;
     25 import android.os.SystemClock;
     26 import android.test.ActivityInstrumentationTestCase2;
     27 import android.util.DisplayMetrics;
     28 import android.view.MotionEvent;
     29 import android.view.View;
     30 import android.view.ViewConfiguration;
     31 import android.widget.TextView;
     32 
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 import java.util.concurrent.atomic.AtomicBoolean;
     36 
     37 /**
     38  * Verify that gestures dispatched from an accessibility service show up in the current UI
     39  */
     40 public class AccessibilityGestureDispatchTest extends
     41         ActivityInstrumentationTestCase2<AccessibilityGestureDispatchTest.GestureDispatchActivity> {
     42     private static final int GESTURE_COMPLETION_TIMEOUT = 5000; // millis
     43     private static final int MOTION_EVENT_TIMEOUT = 1000; // millis
     44 
     45     final List<MotionEvent> mMotionEvents = new ArrayList<>();
     46     StubGestureAccessibilityService mService;
     47     MyTouchListener mMyTouchListener = new MyTouchListener();
     48     MyGestureCallback mCallback;
     49     TextView mFullScreenTextView;
     50     Rect mViewBounds = new Rect();
     51     boolean mGotUpEvent;
     52     // Without a touch screen, there's no point in testing this feature
     53     boolean mHasTouchScreen;
     54     boolean mHasMultiTouch;
     55 
     56     public AccessibilityGestureDispatchTest() {
     57         super(GestureDispatchActivity.class);
     58     }
     59 
     60     @Override
     61     public void setUp() throws Exception {
     62         super.setUp();
     63 
     64         PackageManager pm = getInstrumentation().getContext().getPackageManager();
     65         mHasTouchScreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
     66                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
     67         if (!mHasTouchScreen) {
     68             return;
     69         }
     70 
     71         mHasMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
     72                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
     73 
     74         mFullScreenTextView =
     75                 (TextView) getActivity().findViewById(R.id.full_screen_text_view);
     76         getInstrumentation().runOnMainSync(() -> {
     77             mFullScreenTextView.getGlobalVisibleRect(mViewBounds);
     78             mFullScreenTextView.setOnTouchListener(mMyTouchListener);
     79         });
     80 
     81         mService = StubGestureAccessibilityService.enableSelf(this);
     82 
     83         mMotionEvents.clear();
     84         mCallback = new MyGestureCallback();
     85         mGotUpEvent = false;
     86     }
     87 
     88     @Override
     89     public void tearDown() throws Exception {
     90         if (!mHasTouchScreen) {
     91             return;
     92         }
     93 
     94         mService.runOnServiceSync(() -> mService.disableSelf());
     95         super.tearDown();
     96     }
     97 
     98     public void testClickAt_producesDownThenUp() throws InterruptedException {
     99         if (!mHasTouchScreen) {
    100             return;
    101         }
    102 
    103         final int clickXInsideView = 10;
    104         final int clickYInsideView = 20;
    105         int clickX = clickXInsideView + mViewBounds.left;
    106         int clickY = clickYInsideView + mViewBounds.top;
    107         GestureDescription click = createClick(clickX, clickY);
    108         mService.runOnServiceSync(() -> mService.doDispatchGesture(click, mCallback, null));
    109         mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
    110         waitForMotionEvents(2);
    111 
    112         assertEquals(2, mMotionEvents.size());
    113         MotionEvent clickDown = mMotionEvents.get(0);
    114         MotionEvent clickUp = mMotionEvents.get(1);
    115 
    116         assertEquals(MotionEvent.ACTION_DOWN, clickDown.getActionMasked());
    117         assertEquals(0, clickDown.getActionIndex());
    118         assertEquals(0, clickDown.getDeviceId());
    119         assertEquals(0, clickDown.getEdgeFlags());
    120         assertEquals(1F, clickDown.getXPrecision());
    121         assertEquals(1F, clickDown.getYPrecision());
    122         assertEquals(1, clickDown.getPointerCount());
    123         assertEquals(1F, clickDown.getPressure());
    124         assertEquals((float) clickXInsideView, clickDown.getX());
    125         assertEquals((float) clickYInsideView, clickDown.getY());
    126         assertEquals(clickDown.getDownTime(), clickDown.getEventTime());
    127 
    128         assertEquals(MotionEvent.ACTION_UP, clickUp.getActionMasked());
    129         assertEquals(clickDown.getDownTime(), clickUp.getDownTime());
    130         assertEquals(ViewConfiguration.getTapTimeout(),
    131                 clickUp.getEventTime() - clickUp.getDownTime());
    132         assertTrue(clickDown.getEventTime() + ViewConfiguration.getLongPressTimeout()
    133                 > clickUp.getEventTime());
    134         assertEquals((float) clickXInsideView, clickUp.getX());
    135         assertEquals((float) clickYInsideView, clickUp.getY());
    136     }
    137 
    138     public void testLongClickAt_producesEventsWithLongClickTiming() throws InterruptedException {
    139         if (!mHasTouchScreen) {
    140             return;
    141         }
    142 
    143         final int clickXInsideView = 10;
    144         final int clickYInsideView = 20;
    145         int clickX = clickXInsideView + mViewBounds.left;
    146         int clickY = clickYInsideView + mViewBounds.top;
    147         GestureDescription longClick = createLongClick(clickX, clickY);
    148         mService.runOnServiceSync(() -> mService.doDispatchGesture(longClick, mCallback, null));
    149         mCallback.assertGestureCompletes(
    150                 ViewConfiguration.getLongPressTimeout() + GESTURE_COMPLETION_TIMEOUT);
    151 
    152         waitForMotionEvents(2);
    153         MotionEvent clickDown = mMotionEvents.get(0);
    154         MotionEvent clickUp = mMotionEvents.get(1);
    155 
    156         assertEquals(MotionEvent.ACTION_DOWN, clickDown.getActionMasked());
    157 
    158         assertEquals((float) clickXInsideView, clickDown.getX());
    159         assertEquals((float) clickYInsideView, clickDown.getY());
    160 
    161         assertEquals(MotionEvent.ACTION_UP, clickUp.getActionMasked());
    162         assertTrue(clickDown.getEventTime() + ViewConfiguration.getLongPressTimeout()
    163                 <= clickUp.getEventTime());
    164         assertEquals(clickDown.getDownTime(), clickUp.getDownTime());
    165         assertEquals((float) clickXInsideView, clickUp.getX());
    166         assertEquals((float) clickYInsideView, clickUp.getY());
    167     }
    168 
    169     public void testSwipe_shouldContainPointsInALine() throws InterruptedException {
    170         if (!mHasTouchScreen) {
    171             return;
    172         }
    173 
    174         int startXInsideView = 10;
    175         int startYInsideView = 20;
    176         int endXInsideView = 20;
    177         int endYInsideView = 40;
    178         int startX = startXInsideView + mViewBounds.left;
    179         int startY = startYInsideView + mViewBounds.top;
    180         int endX = endXInsideView + mViewBounds.left;
    181         int endY = endYInsideView + mViewBounds.top;
    182         int gestureTime = 500;
    183         float swipeTolerance = 2.0f;
    184 
    185         GestureDescription swipe = createSwipe(startX, startY, endX, endY, gestureTime);
    186         mService.runOnServiceSync(() -> mService.doDispatchGesture(swipe, mCallback, null));
    187         mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
    188         waitForUpEvent();
    189         int numEvents = mMotionEvents.size();
    190 
    191         MotionEvent downEvent = mMotionEvents.get(0);
    192         assertEquals(MotionEvent.ACTION_DOWN, downEvent.getActionMasked());
    193         assertEquals(startXInsideView, (int) downEvent.getX());
    194         assertEquals(startYInsideView, (int) downEvent.getY());
    195 
    196         MotionEvent upEvent = mMotionEvents.get(numEvents - 1);
    197         assertEquals(MotionEvent.ACTION_UP, upEvent.getActionMasked());
    198         assertEquals(endXInsideView, (int) upEvent.getX());
    199         assertEquals(endYInsideView, (int) upEvent.getY());
    200         assertEquals(gestureTime, upEvent.getEventTime() - downEvent.getEventTime());
    201 
    202         long lastEventTime = downEvent.getEventTime();
    203         for (int i = 1; i < numEvents - 1; i++) {
    204             MotionEvent moveEvent = mMotionEvents.get(i);
    205             assertEquals(MotionEvent.ACTION_MOVE, moveEvent.getActionMasked());
    206             assertTrue(moveEvent.getEventTime() >= lastEventTime);
    207             float fractionOfSwipe =
    208                     ((float) (moveEvent.getEventTime() - downEvent.getEventTime())) / gestureTime;
    209             float fractionX = ((float) (endXInsideView - startXInsideView)) * fractionOfSwipe;
    210             float fractionY = ((float) (endYInsideView - startYInsideView)) * fractionOfSwipe;
    211             assertEquals(startXInsideView + fractionX, moveEvent.getX(), swipeTolerance);
    212             assertEquals(startYInsideView + fractionY, moveEvent.getY(), swipeTolerance);
    213             lastEventTime = moveEvent.getEventTime();
    214         }
    215     }
    216 
    217     public void testSlowSwipe_shouldNotContainMovesForTinyMovement() throws InterruptedException {
    218         if (!mHasTouchScreen) {
    219             return;
    220         }
    221 
    222         int startXInsideView = 10;
    223         int startYInsideView = 20;
    224         int endXInsideView = 11;
    225         int endYInsideView = 22;
    226         int startX = startXInsideView + mViewBounds.left;
    227         int startY = startYInsideView + mViewBounds.top;
    228         int endX = endXInsideView + mViewBounds.left;
    229         int endY = endYInsideView + mViewBounds.top;
    230         int gestureTime = 1000;
    231 
    232         GestureDescription swipe = createSwipe(startX, startY, endX, endY, gestureTime);
    233         mService.runOnServiceSync(() -> mService.doDispatchGesture(swipe, mCallback, null));
    234         mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
    235         waitForUpEvent();
    236 
    237         assertEquals(5, mMotionEvents.size());
    238 
    239         assertEquals(MotionEvent.ACTION_DOWN, mMotionEvents.get(0).getActionMasked());
    240         assertEquals(MotionEvent.ACTION_MOVE, mMotionEvents.get(1).getActionMasked());
    241         assertEquals(MotionEvent.ACTION_MOVE, mMotionEvents.get(2).getActionMasked());
    242         assertEquals(MotionEvent.ACTION_MOVE, mMotionEvents.get(3).getActionMasked());
    243         assertEquals(MotionEvent.ACTION_UP, mMotionEvents.get(4).getActionMasked());
    244 
    245         assertEquals(startXInsideView, (int) mMotionEvents.get(0).getX());
    246         assertEquals(startXInsideView, (int) mMotionEvents.get(1).getX());
    247         assertEquals(startXInsideView + 1, (int) mMotionEvents.get(2).getX());
    248         assertEquals(startXInsideView + 1, (int) mMotionEvents.get(3).getX());
    249         assertEquals(startXInsideView + 1, (int) mMotionEvents.get(4).getX());
    250 
    251         assertEquals(startYInsideView, (int) mMotionEvents.get(0).getY());
    252         assertEquals(startYInsideView + 1, (int) mMotionEvents.get(1).getY());
    253         assertEquals(startYInsideView + 1, (int) mMotionEvents.get(2).getY());
    254         assertEquals(startYInsideView + 2, (int) mMotionEvents.get(3).getY());
    255         assertEquals(startYInsideView + 2, (int) mMotionEvents.get(4).getY());
    256     }
    257 
    258     public void testAngledPinch_looksReasonable() throws InterruptedException {
    259         if (!(mHasTouchScreen && mHasMultiTouch)) {
    260             return;
    261         }
    262 
    263         int centerXInsideView = 50;
    264         int centerYInsideView = 60;
    265         int centerX = centerXInsideView + mViewBounds.left;
    266         int centerY = centerYInsideView + mViewBounds.top;
    267         int startSpacing = 100;
    268         int endSpacing = 50;
    269         int gestureTime = 500;
    270         float pinchTolerance = 2.0f;
    271 
    272         GestureDescription pinch = createPinch(centerX, centerY, startSpacing,
    273                 endSpacing, 45.0F, gestureTime);
    274         mService.runOnServiceSync(() -> mService.doDispatchGesture(pinch, mCallback, null));
    275         mCallback.assertGestureCompletes(gestureTime + GESTURE_COMPLETION_TIMEOUT);
    276         waitForUpEvent();
    277         int numEvents = mMotionEvents.size();
    278 
    279         // First two events are the initial down and the pointer down
    280         assertEquals(MotionEvent.ACTION_DOWN, mMotionEvents.get(0).getActionMasked());
    281         assertEquals(MotionEvent.ACTION_POINTER_DOWN, mMotionEvents.get(1).getActionMasked());
    282 
    283         // The second event must have two pointers at the initial spacing along a 45 degree angle
    284         MotionEvent firstEventWithTwoPointers = mMotionEvents.get(1);
    285         assertEquals(2, firstEventWithTwoPointers.getPointerCount());
    286         MotionEvent.PointerCoords coords0 = new MotionEvent.PointerCoords();
    287         MotionEvent.PointerCoords coords1 = new MotionEvent.PointerCoords();
    288         firstEventWithTwoPointers.getPointerCoords(0, coords0);
    289         firstEventWithTwoPointers.getPointerCoords(1, coords1);
    290         // Verify center point
    291         assertEquals((float) centerXInsideView, (coords0.x + coords1.x) / 2, pinchTolerance);
    292         assertEquals((float) centerYInsideView, (coords0.y + coords1.y) / 2, pinchTolerance);
    293         // Verify angle
    294         assertEquals(coords0.x - centerXInsideView, coords0.y - centerYInsideView, pinchTolerance);
    295         assertEquals(coords1.x - centerXInsideView, coords1.y - centerYInsideView, pinchTolerance);
    296         // Verify spacing
    297         assertEquals(startSpacing, distance(coords0, coords1), pinchTolerance);
    298 
    299         // The last two events are the pointer up and the final up
    300         assertEquals(MotionEvent.ACTION_UP, mMotionEvents.get(numEvents - 1).getActionMasked());
    301 
    302         MotionEvent lastEventWithTwoPointers = mMotionEvents.get(numEvents - 2);
    303         assertEquals(MotionEvent.ACTION_POINTER_UP, lastEventWithTwoPointers.getActionMasked());
    304         lastEventWithTwoPointers.getPointerCoords(0, coords0);
    305         lastEventWithTwoPointers.getPointerCoords(1, coords1);
    306         // Verify center point
    307         assertEquals((float) centerXInsideView, (coords0.x + coords1.x) / 2, pinchTolerance);
    308         assertEquals((float) centerYInsideView, (coords0.y + coords1.y) / 2, pinchTolerance);
    309         // Verify angle
    310         assertEquals(coords0.x - centerXInsideView, coords0.y - centerYInsideView, pinchTolerance);
    311         assertEquals(coords1.x - centerXInsideView, coords1.y - centerYInsideView, pinchTolerance);
    312         // Verify spacing
    313         assertEquals(endSpacing, distance(coords0, coords1), pinchTolerance);
    314 
    315         float lastSpacing = startSpacing;
    316         for (int i = 2; i < numEvents - 2; i++) {
    317             MotionEvent eventInMiddle = mMotionEvents.get(i);
    318             assertEquals(MotionEvent.ACTION_MOVE, eventInMiddle.getActionMasked());
    319             eventInMiddle.getPointerCoords(0, coords0);
    320             eventInMiddle.getPointerCoords(1, coords1);
    321             // Verify center point
    322             assertEquals((float) centerXInsideView, (coords0.x + coords1.x) / 2, pinchTolerance);
    323             assertEquals((float) centerYInsideView, (coords0.y + coords1.y) / 2, pinchTolerance);
    324             // Verify angle
    325             assertEquals(coords0.x - centerXInsideView, coords0.y - centerYInsideView,
    326                     pinchTolerance);
    327             assertEquals(coords1.x - centerXInsideView, coords1.y - centerYInsideView,
    328                     pinchTolerance);
    329             float spacing = distance(coords0, coords1);
    330             assertTrue(spacing <= lastSpacing + pinchTolerance);
    331             assertTrue(spacing >= endSpacing - pinchTolerance);
    332             lastSpacing = spacing;
    333         }
    334     }
    335 
    336     public void testClickWhenMagnified_matchesActualTouch() throws InterruptedException {
    337         if (!mHasTouchScreen) {
    338             return;
    339         }
    340 
    341         final int clickXInsideView = 10;
    342         final int clickYInsideView = 20;
    343         int clickX = clickXInsideView + mViewBounds.left;
    344         int clickY = clickYInsideView + mViewBounds.top;
    345         final float TOUCH_TOLERANCE = 2.0f;
    346 
    347         StubMagnificationAccessibilityService magnificationService =
    348                 StubMagnificationAccessibilityService.enableSelf(this);
    349         android.accessibilityservice.AccessibilityService.MagnificationController
    350                 magnificationController = magnificationService.getMagnificationController();
    351         final Resources res = getInstrumentation().getTargetContext().getResources();
    352         final DisplayMetrics metrics = res.getDisplayMetrics();
    353         try {
    354             // Magnify screen by 2x from upper left corner
    355             final AtomicBoolean setScale = new AtomicBoolean();
    356             final float magnificationFactor = 2.0f;
    357             // Center to have (0,0) in the upper-left corner
    358             final float centerX = metrics.widthPixels / (2.0f * magnificationFactor) - 1.0f;
    359             final float centerY = metrics.heightPixels / (2.0f * magnificationFactor) - 1.0f;
    360             magnificationService.runOnServiceSync(() -> {
    361                         setScale.set(magnificationController.setScale(magnificationFactor, false));
    362                         // Make sure the upper right corner is on the screen
    363                         magnificationController.setCenter(centerX, centerY, false);
    364                     });
    365             assertTrue("Failed to set scale", setScale.get());
    366 
    367             GestureDescription click = createClick((int) (clickX * magnificationFactor),
    368                     (int) (clickY * magnificationFactor));
    369             mService.runOnServiceSync(() -> mService.doDispatchGesture(click, mCallback, null));
    370             mCallback.assertGestureCompletes(GESTURE_COMPLETION_TIMEOUT);
    371             waitForMotionEvents(3);
    372         } finally {
    373             // Reset magnification
    374             final AtomicBoolean result = new AtomicBoolean();
    375             magnificationService.runOnServiceSync(() ->
    376                     result.set(magnificationController.reset(false)));
    377             magnificationService.runOnServiceSync(() -> magnificationService.disableSelf());
    378             assertTrue("Failed to reset", result.get());
    379         }
    380 
    381         assertEquals(2, mMotionEvents.size());
    382         MotionEvent clickDown = mMotionEvents.get(0);
    383         MotionEvent clickUp = mMotionEvents.get(1);
    384 
    385         assertEquals(MotionEvent.ACTION_DOWN, clickDown.getActionMasked());
    386         assertEquals((float) clickXInsideView, clickDown.getX(), TOUCH_TOLERANCE);
    387         assertEquals((float) clickYInsideView, clickDown.getY(), TOUCH_TOLERANCE);
    388         assertEquals(clickDown.getDownTime(), clickDown.getEventTime());
    389 
    390         assertEquals(MotionEvent.ACTION_UP, clickUp.getActionMasked());
    391         assertEquals((float) clickXInsideView, clickUp.getX(), TOUCH_TOLERANCE);
    392         assertEquals((float) clickYInsideView, clickUp.getY(), TOUCH_TOLERANCE);
    393     }
    394 
    395 
    396     public static class GestureDispatchActivity extends AccessibilityTestActivity {
    397         public GestureDispatchActivity() {
    398             super();
    399         }
    400 
    401         @Override
    402         public void onCreate(Bundle savedInstanceState) {
    403             super.onCreate(savedInstanceState);
    404             setContentView(R.layout.full_screen_frame_layout);
    405         }
    406     }
    407 
    408     public static class MyGestureCallback extends AccessibilityService.GestureResultCallback {
    409         private boolean mCompleted;
    410         private boolean mCancelled;
    411 
    412         @Override
    413         public synchronized void onCompleted(GestureDescription gestureDescription) {
    414             mCompleted = true;
    415             notifyAll();
    416         }
    417 
    418         @Override
    419         public synchronized void onCancelled(GestureDescription gestureDescription) {
    420             mCancelled = true;
    421             notifyAll();
    422         }
    423 
    424         public synchronized void assertGestureCompletes(long timeout) {
    425             if (mCompleted) {
    426                 return;
    427             }
    428             try {
    429                 wait(timeout);
    430             } catch (InterruptedException e) {
    431                 throw new RuntimeException(e);
    432             }
    433             assertTrue("Gesture did not complete.", mCompleted);
    434         }
    435     }
    436 
    437     private void waitForMotionEvents(int numEventsExpected) throws InterruptedException {
    438         synchronized (mMotionEvents) {
    439             long endMillis = SystemClock.uptimeMillis() + MOTION_EVENT_TIMEOUT;
    440             while ((mMotionEvents.size() < numEventsExpected)
    441                     && (SystemClock.uptimeMillis() < endMillis)) {
    442                 mMotionEvents.wait(endMillis - SystemClock.uptimeMillis());
    443             }
    444         }
    445     }
    446 
    447     private void waitForUpEvent() throws InterruptedException {
    448         synchronized (mMotionEvents) {
    449             long endMillis = SystemClock.uptimeMillis() + MOTION_EVENT_TIMEOUT;
    450             while (!mGotUpEvent && (SystemClock.uptimeMillis() < endMillis)) {
    451                 mMotionEvents.wait(endMillis - SystemClock.uptimeMillis());
    452             }
    453         }
    454     }
    455 
    456     private float distance(MotionEvent.PointerCoords point1, MotionEvent.PointerCoords point2) {
    457         return (float) Math.hypot((double) (point1.x - point2.x), (double) (point1.y - point2.y));
    458     }
    459 
    460     private class MyTouchListener implements View.OnTouchListener {
    461         @Override
    462         public boolean onTouch(View view, MotionEvent motionEvent) {
    463             synchronized (mMotionEvents) {
    464                 if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
    465                     mGotUpEvent = true;
    466                 }
    467                 mMotionEvents.add(MotionEvent.obtain(motionEvent));
    468                 mMotionEvents.notifyAll();
    469                 return true;
    470             }
    471         }
    472     }
    473 
    474     private GestureDescription createClick(int x, int y) {
    475         Path clickPath = new Path();
    476         clickPath.moveTo(x, y);
    477         GestureDescription.StrokeDescription clickStroke =
    478                 new GestureDescription.StrokeDescription(clickPath, 0, ViewConfiguration.getTapTimeout());
    479         GestureDescription.Builder clickBuilder = new GestureDescription.Builder();
    480         clickBuilder.addStroke(clickStroke);
    481         return clickBuilder.build();
    482     }
    483 
    484     private GestureDescription createLongClick(int x, int y) {
    485         Path clickPath = new Path();
    486         clickPath.moveTo(x, y);
    487         int longPressTime = ViewConfiguration.getLongPressTimeout();
    488 
    489         GestureDescription.StrokeDescription longClickStroke =
    490                 new GestureDescription.StrokeDescription(clickPath, 0, longPressTime + (longPressTime / 2));
    491         GestureDescription.Builder longClickBuilder = new GestureDescription.Builder();
    492         longClickBuilder.addStroke(longClickStroke);
    493         return longClickBuilder.build();
    494     }
    495 
    496     private GestureDescription createSwipe(
    497             int startX, int startY, int endX, int endY, long duration) {
    498         Path swipePath = new Path();
    499         swipePath.moveTo(startX, startY);
    500         swipePath.lineTo(endX, endY);
    501 
    502         GestureDescription.StrokeDescription swipeStroke = new GestureDescription.StrokeDescription(swipePath, 0, duration);
    503         GestureDescription.Builder swipeBuilder = new GestureDescription.Builder();
    504         swipeBuilder.addStroke(swipeStroke);
    505         return swipeBuilder.build();
    506     }
    507 
    508     private GestureDescription createPinch(int centerX, int centerY, int startSpacing,
    509             int endSpacing, float orientation, long duration) {
    510         if ((startSpacing < 0) || (endSpacing < 0)) {
    511             throw new IllegalArgumentException("Pinch spacing cannot be negative");
    512         }
    513         float[] startPoint1 = new float[2];
    514         float[] endPoint1 = new float[2];
    515         float[] startPoint2 = new float[2];
    516         float[] endPoint2 = new float[2];
    517 
    518         /* Build points for a horizontal gesture centered at the origin */
    519         startPoint1[0] = startSpacing / 2;
    520         startPoint1[1] = 0;
    521         endPoint1[0] = endSpacing / 2;
    522         endPoint1[1] = 0;
    523         startPoint2[0] = -startSpacing / 2;
    524         startPoint2[1] = 0;
    525         endPoint2[0] = -endSpacing / 2;
    526         endPoint2[1] = 0;
    527 
    528         /* Rotate and translate the points */
    529         Matrix matrix = new Matrix();
    530         matrix.setRotate(orientation);
    531         matrix.postTranslate(centerX, centerY);
    532         matrix.mapPoints(startPoint1);
    533         matrix.mapPoints(endPoint1);
    534         matrix.mapPoints(startPoint2);
    535         matrix.mapPoints(endPoint2);
    536 
    537         Path path1 = new Path();
    538         path1.moveTo(startPoint1[0], startPoint1[1]);
    539         path1.lineTo(endPoint1[0], endPoint1[1]);
    540         Path path2 = new Path();
    541         path2.moveTo(startPoint2[0], startPoint2[1]);
    542         path2.lineTo(endPoint2[0], endPoint2[1]);
    543 
    544         GestureDescription.StrokeDescription path1Stroke = new GestureDescription.StrokeDescription(path1, 0, duration);
    545         GestureDescription.StrokeDescription path2Stroke = new GestureDescription.StrokeDescription(path2, 0, duration);
    546         GestureDescription.Builder swipeBuilder = new GestureDescription.Builder();
    547         swipeBuilder.addStroke(path1Stroke);
    548         swipeBuilder.addStroke(path2Stroke);
    549         return swipeBuilder.build();
    550     }
    551 }
    552