Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f165944 authored by Phil Weaver's avatar Phil Weaver
Browse files

Support continuing dispatched a11y gestures.

Test: Ran the tests in this CL, as well as new CTS tests for
the new API.

Bug: 29477207
Change-Id: Ie5aba553286e954f7afe76ccfa97a7e8be9d75af
parent 0f10b2f1
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -2799,9 +2799,14 @@ package android.accessibilityservice {
  public static class GestureDescription.StrokeDescription {
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long, int, boolean);
    method public int getContinuedStrokeId();
    method public long getDuration();
    method public int getId();
    method public android.graphics.Path getPath();
    method public long getStartTime();
    method public boolean isContinued();
    field public static final int INVALID_STROKE_ID = -1; // 0xffffffff
  }
}
+5 −0
Original line number Diff line number Diff line
@@ -2913,9 +2913,14 @@ package android.accessibilityservice {
  public static class GestureDescription.StrokeDescription {
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long, int, boolean);
    method public int getContinuedStrokeId();
    method public long getDuration();
    method public int getId();
    method public android.graphics.Path getPath();
    method public long getStartTime();
    method public boolean isContinued();
    field public static final int INVALID_STROKE_ID = -1; // 0xffffffff
  }
}
+5 −0
Original line number Diff line number Diff line
@@ -2799,9 +2799,14 @@ package android.accessibilityservice {
  public static class GestureDescription.StrokeDescription {
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long, int, boolean);
    method public int getContinuedStrokeId();
    method public long getDuration();
    method public int getId();
    method public android.graphics.Path getPath();
    method public long getStartTime();
    method public boolean isContinued();
    field public static final int INVALID_STROKE_ID = -1; // 0xffffffff
  }
}
+85 −186
Original line number Diff line number Diff line
@@ -23,10 +23,6 @@ import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;

import java.util.ArrayList;
import java.util.List;
@@ -128,9 +124,14 @@ public final class GestureDescription {
        for (int i = 0; i < mStrokes.size(); i++) {
            StrokeDescription strokeDescription = mStrokes.get(i);
            if (strokeDescription.hasPointForTime(time)) {
                touchPoints[numPointsFound].mPathIndex = i;
                touchPoints[numPointsFound].mIsStartOfPath = (time == strokeDescription.mStartTime);
                touchPoints[numPointsFound].mIsEndOfPath = (time == strokeDescription.mEndTime);
                touchPoints[numPointsFound].mStrokeId = strokeDescription.getId();
                touchPoints[numPointsFound].mContinuedStrokeId =
                        strokeDescription.getContinuedStrokeId();
                touchPoints[numPointsFound].mIsStartOfPath =
                        (strokeDescription.getContinuedStrokeId() < 0)
                                && (time == strokeDescription.mStartTime);
                touchPoints[numPointsFound].mIsEndOfPath = !strokeDescription.isContinued()
                        && (time == strokeDescription.mEndTime);
                strokeDescription.getPosForTime(time, mTempPos);
                touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
                touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
@@ -196,6 +197,10 @@ public final class GestureDescription {
     * Immutable description of stroke that can be part of a gesture.
     */
    public static class StrokeDescription {
        public static final int INVALID_STROKE_ID = -1;

        static int sIdCounter;

        Path mPath;
        long mStartTime;
        long mEndTime;
@@ -203,6 +208,9 @@ public final class GestureDescription {
        private PathMeasure mPathMeasure;
        // The tap location is only set for zero-length paths
        float[] mTapLocation;
        int mId;
        boolean mContinued;
        int mContinuedStrokeId;

        /**
         * @param path The path to follow. Must have exactly one contour. The bounds of the path
@@ -216,6 +224,32 @@ public final class GestureDescription {
        public StrokeDescription(@NonNull Path path,
                @IntRange(from = 0) long startTime,
                @IntRange(from = 0) long duration) {
            this(path, startTime, duration, INVALID_STROKE_ID, false);
        }

        /**
         * @param path The path to follow. Must have exactly one contour. The bounds of the path
         * must not be negative. The path must not be empty. If the path has zero length
         * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
         * @param startTime The time, in milliseconds, from the time the gesture starts to the
         * time the stroke should start. Must not be negative.
         * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
         * Must be positive.
         * @param continuedStrokeId The ID of the stroke that this stroke continues, or
         * {@link #INVALID_STROKE_ID} if it continues no stroke. The stroke it
         * continues must have its isContinued flag set to {@code true} and must be in the
         * gesture dispatched immediately before the one containing this stroke.
         * @param isContinued {@code true} if this stroke will be continued by one in the
         * next gesture {@code false} otherwise. Continued strokes keep their pointers down when
         * the gesture completes.
         */
        public StrokeDescription(@NonNull Path path,
                @IntRange(from = 0) long startTime,
                @IntRange(from = 0) long duration,
                @IntRange(from = 0) int continuedStrokeId,
                boolean isContinued) {
            mContinued = isContinued;
            mContinuedStrokeId = continuedStrokeId;
            if (duration <= 0) {
                throw new IllegalArgumentException("Duration must be positive");
            }
@@ -252,6 +286,7 @@ public final class GestureDescription {
            mStartTime = startTime;
            mEndTime = startTime + duration;
            mTimeToLengthConversion = getLength() / duration;
            mId = sIdCounter++;
        }

        /**
@@ -281,6 +316,34 @@ public final class GestureDescription {
            return mEndTime - mStartTime;
        }

        /**
         * Get the stroke's ID. The ID is used when a stroke is to be continued by another
         * stroke in a future gesture.
         *
         * @return the ID of this stroke
         */
        public int getId() {
            return mId;
        }

        /**
         * Check if this stroke is marked to continue in the next gesture.
         *
         * @return {@code true} if the stroke is to be continued.
         */
        public boolean isContinued() {
            return mContinued;
        }

        /**
         * Get the ID of the stroke that this one will continue.
         *
         * @return The ID of the stroke that this stroke continues, or 0 if no such stroke exists.
         */
        public int getContinuedStrokeId() {
            return mContinuedStrokeId;
        }

        float getLength() {
            return mPathMeasure.getLength();
        }
@@ -314,11 +377,12 @@ public final class GestureDescription {
        private static final int FLAG_IS_START_OF_PATH = 0x01;
        private static final int FLAG_IS_END_OF_PATH = 0x02;

        int mPathIndex;
        boolean mIsStartOfPath;
        boolean mIsEndOfPath;
        float mX;
        float mY;
        public int mStrokeId;
        public int mContinuedStrokeId;
        public boolean mIsStartOfPath;
        public boolean mIsEndOfPath;
        public float mX;
        public float mY;

        public TouchPoint() {
        }
@@ -328,7 +392,8 @@ public final class GestureDescription {
        }

        public TouchPoint(Parcel parcel) {
            mPathIndex = parcel.readInt();
            mStrokeId = parcel.readInt();
            mContinuedStrokeId = parcel.readInt();
            int startEnd = parcel.readInt();
            mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
            mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
@@ -336,8 +401,9 @@ public final class GestureDescription {
            mY = parcel.readFloat();
        }

        void copyFrom(TouchPoint other) {
            mPathIndex = other.mPathIndex;
        public void copyFrom(TouchPoint other) {
            mStrokeId = other.mStrokeId;
            mContinuedStrokeId = other.mContinuedStrokeId;
            mIsStartOfPath = other.mIsStartOfPath;
            mIsEndOfPath = other.mIsEndOfPath;
            mX = other.mX;
@@ -351,7 +417,8 @@ public final class GestureDescription {

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mPathIndex);
            dest.writeInt(mStrokeId);
            dest.writeInt(mContinuedStrokeId);
            int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
            startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
            dest.writeInt(startEnd);
@@ -426,30 +493,15 @@ public final class GestureDescription {
    }

    /**
     * Class to convert a GestureDescription to a series of MotionEvents.
     * Class to convert a GestureDescription to a series of GestureSteps.
     *
     * @hide
     */
    public static class MotionEventGenerator {
        /**
         * Constants used to initialize all MotionEvents
         */
        private static final int EVENT_META_STATE = 0;
        private static final int EVENT_BUTTON_STATE = 0;
        private static final int EVENT_DEVICE_ID = 0;
        private static final int EVENT_EDGE_FLAGS = 0;
        private static final int EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
        private static final int EVENT_FLAGS = 0;
        private static final float EVENT_X_PRECISION = 1;
        private static final float EVENT_Y_PRECISION = 1;

        /* Lazily-created scratch memory for processing touches */
        private static TouchPoint[] sCurrentTouchPoints;
        private static TouchPoint[] sLastTouchPoints;
        private static PointerCoords[] sPointerCoords;
        private static PointerProperties[] sPointerProps;

        static List<GestureStep> getGestureStepsFromGestureDescription(
        public static List<GestureStep> getGestureStepsFromGestureDescription(
                GestureDescription description, int sampleTimeMs) {
            final List<GestureStep> gestureSteps = new ArrayList<>();

@@ -474,31 +526,6 @@ public final class GestureDescription {
            return gestureSteps;
        }

        public static List<MotionEvent> getMotionEventsFromGestureSteps(List<GestureStep> steps) {
            final List<MotionEvent> motionEvents = new ArrayList<>();

            // Number of points in last touch event
            int lastTouchPointSize = 0;
            TouchPoint[] lastTouchPoints;

            for (int i = 0; i < steps.size(); i++) {
                GestureStep step = steps.get(i);
                int currentTouchPointSize = step.numTouchPoints;
                lastTouchPoints = getLastTouchPoints(
                        Math.max(lastTouchPointSize, currentTouchPointSize));

                appendMoveEventIfNeeded(motionEvents, lastTouchPoints, lastTouchPointSize,
                        step.touchPoints, currentTouchPointSize, step.timeSinceGestureStart);
                lastTouchPointSize = appendUpEvents(motionEvents, lastTouchPoints,
                        lastTouchPointSize, step.touchPoints, currentTouchPointSize,
                        step.timeSinceGestureStart);
                lastTouchPointSize = appendDownEvents(motionEvents, lastTouchPoints,
                        lastTouchPointSize, step.touchPoints, currentTouchPointSize,
                        step.timeSinceGestureStart);
            }
            return motionEvents;
        }

        private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) {
            if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) {
                sCurrentTouchPoints = new TouchPoint[requiredCapacity];
@@ -508,133 +535,5 @@ public final class GestureDescription {
            }
            return sCurrentTouchPoints;
        }

        private static TouchPoint[] getLastTouchPoints(int requiredCapacity) {
            if ((sLastTouchPoints == null) || (sLastTouchPoints.length < requiredCapacity)) {
                sLastTouchPoints = new TouchPoint[requiredCapacity];
                for (int i = 0; i < requiredCapacity; i++) {
                    sLastTouchPoints[i] = new TouchPoint();
                }
            }
            return sLastTouchPoints;
        }

        private static PointerCoords[] getPointerCoords(int requiredCapacity) {
            if ((sPointerCoords == null) || (sPointerCoords.length < requiredCapacity)) {
                sPointerCoords = new PointerCoords[requiredCapacity];
                for (int i = 0; i < requiredCapacity; i++) {
                    sPointerCoords[i] = new PointerCoords();
                }
            }
            return sPointerCoords;
        }

        private static PointerProperties[] getPointerProps(int requiredCapacity) {
            if ((sPointerProps == null) || (sPointerProps.length < requiredCapacity)) {
                sPointerProps = new PointerProperties[requiredCapacity];
                for (int i = 0; i < requiredCapacity; i++) {
                    sPointerProps[i] = new PointerProperties();
                }
            }
            return sPointerProps;
        }

        private static void appendMoveEventIfNeeded(List<MotionEvent> motionEvents,
                TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
                TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
            /* Look for pointers that have moved */
            boolean moveFound = false;
            for (int i = 0; i < currentTouchPointsSize; i++) {
                int lastPointsIndex = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize,
                        currentTouchPoints[i].mPathIndex);
                if (lastPointsIndex >= 0) {
                    moveFound |= (lastTouchPoints[lastPointsIndex].mX != currentTouchPoints[i].mX)
                            || (lastTouchPoints[lastPointsIndex].mY != currentTouchPoints[i].mY);
                    lastTouchPoints[lastPointsIndex].copyFrom(currentTouchPoints[i]);
                }
            }

            if (moveFound) {
                long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime();
                motionEvents.add(obtainMotionEvent(downTime, currentTime, MotionEvent.ACTION_MOVE,
                        lastTouchPoints, lastTouchPointsSize));
            }
        }

        private static int appendUpEvents(List<MotionEvent> motionEvents,
                TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
                TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
            /* Look for a pointer at the end of its path */
            for (int i = 0; i < currentTouchPointsSize; i++) {
                if (currentTouchPoints[i].mIsEndOfPath) {
                    int indexOfUpEvent = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize,
                            currentTouchPoints[i].mPathIndex);
                    if (indexOfUpEvent < 0) {
                        continue; // Should not happen
                    }
                    long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime();
                    int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_UP
                            : MotionEvent.ACTION_POINTER_UP;
                    action |= indexOfUpEvent << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                    motionEvents.add(obtainMotionEvent(downTime, currentTime, action,
                            lastTouchPoints, lastTouchPointsSize));
                    /* Remove this point from lastTouchPoints */
                    for (int j = indexOfUpEvent; j < lastTouchPointsSize - 1; j++) {
                        lastTouchPoints[j].copyFrom(lastTouchPoints[j+1]);
                    }
                    lastTouchPointsSize--;
                }
            }
            return lastTouchPointsSize;
        }

        private static int appendDownEvents(List<MotionEvent> motionEvents,
                TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
                TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
            /* Look for a pointer that is just starting */
            for (int i = 0; i < currentTouchPointsSize; i++) {
                if (currentTouchPoints[i].mIsStartOfPath) {
                    /* Add the point to last coords and use the new array to generate the event */
                    lastTouchPoints[lastTouchPointsSize++].copyFrom(currentTouchPoints[i]);
                    int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_DOWN
                            : MotionEvent.ACTION_POINTER_DOWN;
                    long downTime = (action == MotionEvent.ACTION_DOWN) ? currentTime :
                            motionEvents.get(motionEvents.size() - 1).getDownTime();
                    action |= i << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                    motionEvents.add(obtainMotionEvent(downTime, currentTime, action,
                            lastTouchPoints, lastTouchPointsSize));
                }
            }
            return lastTouchPointsSize;
        }

        private static MotionEvent obtainMotionEvent(long downTime, long eventTime, int action,
                TouchPoint[] touchPoints, int touchPointsSize) {
            PointerCoords[] pointerCoords = getPointerCoords(touchPointsSize);
            PointerProperties[] pointerProperties = getPointerProps(touchPointsSize);
            for (int i = 0; i < touchPointsSize; i++) {
                pointerProperties[i].id = touchPoints[i].mPathIndex;
                pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
                pointerCoords[i].clear();
                pointerCoords[i].pressure = 1.0f;
                pointerCoords[i].size = 1.0f;
                pointerCoords[i].x = touchPoints[i].mX;
                pointerCoords[i].y = touchPoints[i].mY;
            }
            return MotionEvent.obtain(downTime, eventTime, action, touchPointsSize,
                    pointerProperties, pointerCoords, EVENT_META_STATE, EVENT_BUTTON_STATE,
                    EVENT_X_PRECISION, EVENT_Y_PRECISION, EVENT_DEVICE_ID, EVENT_EDGE_FLAGS,
                    EVENT_SOURCE, EVENT_FLAGS);
        }

        private static int findPointByPathIndex(TouchPoint[] touchPoints, int touchPointsSize,
                int pathIndex) {
            for (int i = 0; i < touchPointsSize; i++) {
                if (touchPoints[i].mPathIndex == pathIndex) {
                    return i;
                }
            }
            return -1;
        }
    }
}
+2 −9
Original line number Diff line number Diff line
@@ -2843,15 +2843,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
                    }
                    if (mMotionEventInjector != null) {
                        List<GestureDescription.GestureStep> steps = gestureSteps.getList();
                        List<MotionEvent> events = GestureDescription.MotionEventGenerator
                                .getMotionEventsFromGestureSteps(steps);
                        // Confirm that the motion events end with an UP event.
                        if (events.get(events.size() - 1).getAction() == MotionEvent.ACTION_UP) {
                            mMotionEventInjector.injectEvents(events, mServiceInterface, sequence);
                         mMotionEventInjector.injectEvents(steps, mServiceInterface, sequence);
                         return;
                        } else {
                            Slog.e(LOG_TAG, "Gesture is not well-formed");
                        }
                    } else {
                        Slog.e(LOG_TAG, "MotionEventInjector installation timed out");
                    }
Loading