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

Commit a6b64f50 authored by Phil Weaver's avatar Phil Weaver
Browse files

Gesture dispatch from accessibility services.

Add public APIs to describe gestures and dispatch them from
an accessibility service. Added a new capability that
services must declare to have this capability.

Bug: 22514086

Change-Id: I9bff2d9335f0310115112d14b7ed033a6d6c2393
parent f8565afc
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -334,6 +334,7 @@ package android {
    field public static final int calendarViewShown = 16843596; // 0x101034c
    field public static final int calendarViewStyle = 16843613; // 0x101035d
    field public static final int canControlMagnification = 16844040; // 0x1010508
    field public static final int canPerformGestures = 16844046; // 0x101050e
    field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
    field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
    field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2610,6 +2611,7 @@ package android.accessibilityservice {
  public abstract class AccessibilityService extends android.app.Service {
    ctor public AccessibilityService();
    method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
    method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
    method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
    method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2649,6 +2651,12 @@ package android.accessibilityservice {
    field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
  }
  public static abstract class AccessibilityService.GestureResultCallback {
    ctor public AccessibilityService.GestureResultCallback();
    method public void onCancelled(android.accessibilityservice.GestureDescription);
    method public void onCompleted(android.accessibilityservice.GestureDescription);
  }
  public static final class AccessibilityService.MagnificationController {
    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
@@ -2681,6 +2689,7 @@ package android.accessibilityservice {
    method public java.lang.String loadDescription(android.content.pm.PackageManager);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
    field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
    field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
    field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
    field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2707,6 +2716,30 @@ package android.accessibilityservice {
    field public java.lang.String[] packageNames;
  }
  public final class GestureDescription {
    method public static android.accessibilityservice.GestureDescription createClick(int, int);
    method public static android.accessibilityservice.GestureDescription createLongClick(int, int);
    method public static android.accessibilityservice.GestureDescription createPinch(int, int, int, int, float, long);
    method public static android.accessibilityservice.GestureDescription createSwipe(int, int, int, int, long);
    method public android.accessibilityservice.GestureDescription.StrokeDescription getStroke(int);
    method public int getStrokeCount();
    field public static final long MAX_GESTURE_DURATION_MS = 60000L; // 0xea60L
    field public static final int MAX_STROKE_COUNT = 10; // 0xa
  }
  public static class GestureDescription.Builder {
    ctor public GestureDescription.Builder();
    method public android.accessibilityservice.GestureDescription.Builder addStroke(android.accessibilityservice.GestureDescription.StrokeDescription);
    method public android.accessibilityservice.GestureDescription build();
  }
  public static class GestureDescription.StrokeDescription {
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
    method public long getDuration();
    method public android.graphics.Path getPath();
    method public long getStartTime();
  }
}
package android.accounts {
+33 −0
Original line number Diff line number Diff line
@@ -428,6 +428,7 @@ package android {
    field public static final int calendarViewShown = 16843596; // 0x101034c
    field public static final int calendarViewStyle = 16843613; // 0x101035d
    field public static final int canControlMagnification = 16844040; // 0x1010508
    field public static final int canPerformGestures = 16844046; // 0x101050e
    field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
    field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
    field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2711,6 +2712,7 @@ package android.accessibilityservice {
  public abstract class AccessibilityService extends android.app.Service {
    ctor public AccessibilityService();
    method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
    method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
    method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
    method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2750,6 +2752,12 @@ package android.accessibilityservice {
    field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
  }
  public static abstract class AccessibilityService.GestureResultCallback {
    ctor public AccessibilityService.GestureResultCallback();
    method public void onCancelled(android.accessibilityservice.GestureDescription);
    method public void onCompleted(android.accessibilityservice.GestureDescription);
  }
  public static final class AccessibilityService.MagnificationController {
    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
@@ -2782,6 +2790,7 @@ package android.accessibilityservice {
    method public java.lang.String loadDescription(android.content.pm.PackageManager);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
    field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
    field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
    field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
    field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2808,6 +2817,30 @@ package android.accessibilityservice {
    field public java.lang.String[] packageNames;
  }
  public final class GestureDescription {
    method public static android.accessibilityservice.GestureDescription createClick(int, int);
    method public static android.accessibilityservice.GestureDescription createLongClick(int, int);
    method public static android.accessibilityservice.GestureDescription createPinch(int, int, int, int, float, long);
    method public static android.accessibilityservice.GestureDescription createSwipe(int, int, int, int, long);
    method public android.accessibilityservice.GestureDescription.StrokeDescription getStroke(int);
    method public int getStrokeCount();
    field public static final long MAX_GESTURE_DURATION_MS = 60000L; // 0xea60L
    field public static final int MAX_STROKE_COUNT = 10; // 0xa
  }
  public static class GestureDescription.Builder {
    ctor public GestureDescription.Builder();
    method public android.accessibilityservice.GestureDescription.Builder addStroke(android.accessibilityservice.GestureDescription.StrokeDescription);
    method public android.accessibilityservice.GestureDescription build();
  }
  public static class GestureDescription.StrokeDescription {
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
    method public long getDuration();
    method public android.graphics.Path getPath();
    method public long getStartTime();
  }
}
package android.accounts {
+33 −0
Original line number Diff line number Diff line
@@ -334,6 +334,7 @@ package android {
    field public static final int calendarViewShown = 16843596; // 0x101034c
    field public static final int calendarViewStyle = 16843613; // 0x101035d
    field public static final int canControlMagnification = 16844040; // 0x1010508
    field public static final int canPerformGestures = 16844046; // 0x101050e
    field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
    field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
    field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2610,6 +2611,7 @@ package android.accessibilityservice {
  public abstract class AccessibilityService extends android.app.Service {
    ctor public AccessibilityService();
    method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
    method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
    method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
    method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2649,6 +2651,12 @@ package android.accessibilityservice {
    field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
  }
  public static abstract class AccessibilityService.GestureResultCallback {
    ctor public AccessibilityService.GestureResultCallback();
    method public void onCancelled(android.accessibilityservice.GestureDescription);
    method public void onCompleted(android.accessibilityservice.GestureDescription);
  }
  public static final class AccessibilityService.MagnificationController {
    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
    method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
@@ -2681,6 +2689,7 @@ package android.accessibilityservice {
    method public java.lang.String loadDescription(android.content.pm.PackageManager);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
    field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
    field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
    field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
    field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2707,6 +2716,30 @@ package android.accessibilityservice {
    field public java.lang.String[] packageNames;
  }
  public final class GestureDescription {
    method public static android.accessibilityservice.GestureDescription createClick(int, int);
    method public static android.accessibilityservice.GestureDescription createLongClick(int, int);
    method public static android.accessibilityservice.GestureDescription createPinch(int, int, int, int, float, long);
    method public static android.accessibilityservice.GestureDescription createSwipe(int, int, int, int, long);
    method public android.accessibilityservice.GestureDescription.StrokeDescription getStroke(int);
    method public int getStrokeCount();
    field public static final long MAX_GESTURE_DURATION_MS = 60000L; // 0xea60L
    field public static final int MAX_STROKE_COUNT = 10; // 0xa
  }
  public static class GestureDescription.Builder {
    ctor public GestureDescription.Builder();
    method public android.accessibilityservice.GestureDescription.Builder addStroke(android.accessibilityservice.GestureDescription.StrokeDescription);
    method public android.accessibilityservice.GestureDescription build();
  }
  public static class GestureDescription.StrokeDescription {
    ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
    method public long getDuration();
    method public android.graphics.Path getPath();
    method public long getStartTime();
  }
}
package android.accounts {
+144 −3
Original line number Diff line number Diff line
@@ -16,11 +16,13 @@

package android.accessibilityservice;

import android.accessibilityservice.GestureDescription.MotionEventGenerator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Region;
import android.os.Handler;
import android.os.IBinder;
@@ -29,8 +31,11 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityEvent;
@@ -41,10 +46,7 @@ import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

/**
 * An accessibility service runs in the background and receives callbacks by the system
@@ -376,6 +378,7 @@ public abstract class AccessibilityService extends Service {
        public boolean onKeyEvent(KeyEvent event);
        public void onMagnificationChanged(@NonNull Region region,
                float scale, float centerX, float centerY);
        public void onPerformGestureResult(int sequence, boolean completedSuccessfully);
    }

    private int mConnectionId;
@@ -388,6 +391,12 @@ public abstract class AccessibilityService extends Service {

    private MagnificationController mMagnificationController;

    private int mGestureStatusCallbackSequence;

    private SparseArray<GestureResultCallbackInfo> mGestureStatusCallbackInfos;

    private final Object mLock = new Object();

    /**
     * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
     *
@@ -551,6 +560,88 @@ public abstract class AccessibilityService extends Service {
        return mMagnificationController;
    }

    /**
     * Dispatch a gesture to the touch screen. Any gestures currently in progress, whether from
     * the user, this service, or another service, will be cancelled.
     * <p>
     * <strong>Note:</strong> In order to dispatch gestures, your service
     * must declare the capability by setting the
     * {@link android.R.styleable#AccessibilityService_canPerformGestures}
     * property in its meta-data. For more information, see
     * {@link #SERVICE_META_DATA}.
     *
     * @param gesture The gesture to dispatch
     * @param callback The object to call back when the status of the gesture is known. If
     * {@code null}, no status is reported.
     * @param handler The handler on which to call back the {@code callback} object. If
     * {@code null}, the object is called back on the service's main thread.
     *
     * @return {@code true} if the gesture is dispatched, {@code false} if not.
     */
    public final boolean dispatchGesture(@NonNull GestureDescription gesture,
            @Nullable GestureResultCallback callback,
            @Nullable Handler handler) {
        final IAccessibilityServiceConnection connection =
                AccessibilityInteractionClient.getInstance().getConnection(
                        mConnectionId);
        if (connection == null) {
            return false;
        }
        List<MotionEvent> events = MotionEventGenerator.getMotionEventsFromGestureDescription(
                gesture, 100);
        try {
            synchronized (mLock) {
                connection.sendMotionEvents(++mGestureStatusCallbackSequence,
                        new ParceledListSlice<>(events));
                if (callback != null) {
                    if (mGestureStatusCallbackInfos == null) {
                        mGestureStatusCallbackInfos = new SparseArray<>();
                    }
                    GestureResultCallbackInfo callbackInfo = new GestureResultCallbackInfo(gesture,
                            callback, handler);
                    mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo);
                }
            }
        } catch (RemoteException re) {
            throw new RuntimeException(re);
        }
        return true;
    }

    void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
        if (mGestureStatusCallbackInfos == null) {
            return;
        }
        GestureResultCallbackInfo callbackInfo;
        synchronized (mLock) {
            callbackInfo = mGestureStatusCallbackInfos.get(sequence);
        }
        final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
        if ((callbackInfo != null) && (callbackInfo.gestureDescription != null)
                && (callbackInfo.callback != null)) {
            if (callbackInfo.handler != null) {
                callbackInfo.handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (completedSuccessfully) {
                            finalCallbackInfo.callback
                                    .onCompleted(finalCallbackInfo.gestureDescription);
                        } else {
                            finalCallbackInfo.callback
                                    .onCancelled(finalCallbackInfo.gestureDescription);
                        }
                    }
                });
                return;
            }
            if (completedSuccessfully) {
                callbackInfo.callback.onCompleted(callbackInfo.gestureDescription);
            } else {
                callbackInfo.callback.onCancelled(callbackInfo.gestureDescription);
            }
        }
    }

    private void onMagnificationChanged(@NonNull Region region, float scale,
            float centerX, float centerY) {
        if (mMagnificationController != null) {
@@ -1082,6 +1173,11 @@ public abstract class AccessibilityService extends Service {
                    float scale, float centerX, float centerY) {
                AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
            }

            @Override
            public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
                AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
            }
        });
    }

@@ -1100,6 +1196,7 @@ public abstract class AccessibilityService extends Service {
        private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
        private static final int DO_ON_KEY_EVENT = 6;
        private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
        private static final int DO_GESTURE_COMPLETE = 8;

        private final HandlerCaller mCaller;

@@ -1158,6 +1255,12 @@ public abstract class AccessibilityService extends Service {
            mCaller.sendMessage(message);
        }

        public void onPerformGestureResult(int sequence, boolean successfully) {
            Message message = mCaller.obtainMessageII(DO_GESTURE_COMPLETE, sequence,
                    successfully ? 1 : 0);
            mCaller.sendMessage(message);
        }

        @Override
        public void executeMessage(Message message) {
            switch (message.what) {
@@ -1242,9 +1345,47 @@ public abstract class AccessibilityService extends Service {
                    mCallback.onMagnificationChanged(region, scale, centerX, centerY);
                } return;

                case DO_GESTURE_COMPLETE: {
                    final boolean successfully = message.arg2 == 1;
                    mCallback.onPerformGestureResult(message.arg1, successfully);
                } return;

                default :
                    Log.w(LOG_TAG, "Unknown message type " + message.what);
            }
        }
    }

    /**
     * Class used to report status of dispatched gestures
     */
    public static abstract class GestureResultCallback {
        /** Called when the gesture has completed successfully
         *
         * @param gestureDescription The description of the gesture that completed.
         */
        public void onCompleted(GestureDescription gestureDescription) {
        }

        /** Called when the gesture was cancelled
         *
         * @param gestureDescription The description of the gesture that was cancelled.
         */
        public void onCancelled(GestureDescription gestureDescription) {
        }
    }

    /* Object to keep track of gesture result callbacks */
    private static class GestureResultCallbackInfo {
        GestureDescription gestureDescription;
        GestureResultCallback callback;
        Handler handler;

        GestureResultCallbackInfo(GestureDescription gestureDescription,
                GestureResultCallback callback, Handler handler) {
            this.gestureDescription = gestureDescription;
            this.callback = callback;
            this.handler = handler;
        }
    }
}
+21 −6
Original line number Diff line number Diff line
@@ -110,6 +110,12 @@ public class AccessibilityServiceInfo implements Parcelable {
     */
    public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;

    /**
     * Capability: This accessibility service can perform gestures.
     * @see android.R.styleable#AccessibilityService_canPerformGestures
     */
    public static final int CAPABILITY_CAN_PERFORM_GESTURES = 0x00000020;

    private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
            new SparseArray<CapabilityInfo>();
    static {
@@ -133,6 +139,10 @@ public class AccessibilityServiceInfo implements Parcelable {
                new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
                        R.string.capability_title_canControlMagnification,
                        R.string.capability_desc_canControlMagnification));
        sAvailableCapabilityInfos.put(CAPABILITY_CAN_PERFORM_GESTURES,
                new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
                        R.string.capability_title_canPerformGestures,
                        R.string.capability_desc_canPerformGestures));
    }

    /**
@@ -276,12 +286,7 @@ public class AccessibilityServiceInfo implements Parcelable {
    /**
     * This flag requests from the system to filter key events. If this flag
     * is set the accessibility service will receive the key events before
     * applications allowing it implement global shortcuts. Setting this flag
     * does not guarantee that this service will filter key events since only
     * one service can do so at any given time. This avoids user confusion due
     * to behavior change in case different key filtering services are enabled.
     * If there is already another key filtering service enabled, this one will
     * not receive key events.
     * applications allowing it implement global shortcuts.
     * <p>
     * Services that want to set this flag have to declare this capability
     * in their meta-data by setting the attribute {@link android.R.attr
@@ -516,6 +521,10 @@ public class AccessibilityServiceInfo implements Parcelable {
                    .AccessibilityService_canControlMagnification, false)) {
                mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
            }
            if (asAttributes.getBoolean(com.android.internal.R.styleable
                    .AccessibilityService_canPerformGestures, false)) {
                mCapabilities |= CAPABILITY_CAN_PERFORM_GESTURES;
            }
            TypedValue peekedValue = asAttributes.peekValue(
                    com.android.internal.R.styleable.AccessibilityService_description);
            if (peekedValue != null) {
@@ -616,6 +625,8 @@ public class AccessibilityServiceInfo implements Parcelable {
     * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
     * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
     * @see #CAPABILITY_FILTER_KEY_EVENTS
     * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
     * @see #CAPABILITY_CAN_PERFORM_GESTURES
     */
    public int getCapabilities() {
        return mCapabilities;
@@ -631,6 +642,8 @@ public class AccessibilityServiceInfo implements Parcelable {
     * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
     * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
     * @see #CAPABILITY_FILTER_KEY_EVENTS
     * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
     * @see #CAPABILITY_CAN_PERFORM_GESTURES
     *
     * @hide
     */
@@ -933,6 +946,8 @@ public class AccessibilityServiceInfo implements Parcelable {
                return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
            case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
                return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
            case CAPABILITY_CAN_PERFORM_GESTURES:
                return "CAPABILITY_CAN_PERFORM_GESTURES";
            default:
                return "UNKNOWN";
        }
Loading