Add flag for services to request multi-finger gestures.

Bug: 136131815

Test: atest GestureManifoldTest AccessibilityGestureDetectorTest TouchExplorerTest
Change-Id: Ia6eac44f467963856ca7b8ba561794fa5e49b953
diff --git a/api/current.txt b/api/current.txt
index b2c8061..032da42 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2988,6 +2988,7 @@
     field @Deprecated public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
     field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
     field public static final int FLAG_REQUEST_FINGERPRINT_GESTURES = 512; // 0x200
+    field public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 4096; // 0x1000
     field public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 1024; // 0x400
     field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
     field public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 64; // 0x40
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 342b6a5..82c7635 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -351,6 +351,16 @@
      */
     public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x0000800;
 
+    /**
+     * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
+     * multi-finger gestures are also enabled. As a consequence, two-finger bypass gestures will be
+     * disabled. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this flag has no
+     * effect.
+     *
+     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     */
+    public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000;
+
     /** {@hide} */
     public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
 
@@ -1233,6 +1243,8 @@
                 return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
             case FLAG_SERVICE_HANDLES_DOUBLE_TAP:
                 return "FLAG_SERVICE_HANDLES_DOUBLE_TAP";
+            case FLAG_REQUEST_MULTI_FINGER_GESTURES:
+                return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
             case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
                 return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
             case FLAG_REPORT_VIEW_IDS:
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 4b49d67..02b098b 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -105,6 +105,9 @@
     public static final int STATE_FLAG_DISPATCH_DOUBLE_TAP = 0x00000008;
 
     /** @hide */
+    public static final int STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000010;
+
+    /** @hide */
     public static final int DALTONIZER_DISABLED = -1;
 
     /** @hide */
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 643a6ff..20901e0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3716,6 +3716,8 @@
             <flag name="flagRequestShortcutWarningDialogSpokenFeedback" value="0x00000400" />
             <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_SERVICE_HANDLES_DOUBLE_TAP}. -->
             <flag name="flagServiceHandlesDoubleTap" value="0x00000800" />
+            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_MULTI_FINGER_GESTURES}. -->
+            <flag name="flagRequestMultiFingerGestures" value="0x00001000" />
         </attr>
         <!-- Component name of an activity that allows the user to modify
              the settings for this service. This setting cannot be changed at runtime. -->
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index a79ddce..a5877cc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -136,6 +136,8 @@
 
     private boolean mServiceHandlesDoubleTap;
 
+    private boolean mRequestMultiFingerGestures;
+
     boolean mRequestFilterKeyEvents;
 
     boolean mRetrieveInteractiveWindows;
@@ -302,6 +304,8 @@
                 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
         mServiceHandlesDoubleTap = (info.flags
                 & AccessibilityServiceInfo.FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0;
+        mRequestMultiFingerGestures = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0;
         mRequestFilterKeyEvents = (info.flags
                 & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
         mRetrieveInteractiveWindows = (info.flags
@@ -1697,4 +1701,8 @@
     public boolean isServiceHandlesDoubleTapEnabled() {
         return mServiceHandlesDoubleTap;
     }
+
+    public boolean isMultiFingerGesturesEnabled() {
+        return mRequestMultiFingerGestures;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index ee97678..efddd86 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -106,13 +106,21 @@
      */
     static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x00000080;
 
+/**
+     * Flag for enabling multi-finger gestures.
+     *
+     * @see #setUserAndEnabledFeatures(int, int)
+     */
+    static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000100;
+
     static final int FEATURES_AFFECTING_MOTION_EVENTS =
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
                     | FLAG_FEATURE_TOUCH_EXPLORATION
                     | FLAG_FEATURE_SCREEN_MAGNIFIER
                     | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
-                    | FLAG_SERVICE_HANDLES_DOUBLE_TAP;
+                    | FLAG_SERVICE_HANDLES_DOUBLE_TAP
+                    | FLAG_REQUEST_MULTI_FINGER_GESTURES;
 
     private final Context mContext;
 
@@ -405,6 +413,9 @@
                 if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) {
                     explorer.setServiceHandlesDoubleTap(true);
                 }
+                if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
+                    explorer.setMultiFingerGesturesEnabled(true);
+                }
                 addFirstEventHandler(displayId, explorer);
                 mTouchExplorer.put(displayId, explorer);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 560a8ef..61e1adf 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1613,6 +1613,9 @@
                 if (userState.isServiceHandlesDoubleTapEnabledLocked()) {
                     flags |= AccessibilityInputFilter.FLAG_SERVICE_HANDLES_DOUBLE_TAP;
                 }
+                if (userState.isMultiFingerGesturesEnabledLocked()) {
+                    flags |= AccessibilityInputFilter.FLAG_REQUEST_MULTI_FINGER_GESTURES;
+                }
             }
             if (userState.isFilterKeyEventsEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
@@ -1887,18 +1890,19 @@
     private void updateTouchExplorationLocked(AccessibilityUserState userState) {
         boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
         boolean serviceHandlesDoubleTapEnabled = false;
+        boolean requestMultiFingerGestures = false;
         final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
             if (canRequestAndRequestsTouchExplorationLocked(service, userState)) {
                 touchExplorationEnabled = true;
                 serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled();
+                requestMultiFingerGestures = service.isMultiFingerGesturesEnabled();
                 break;
             }
         }
         if (touchExplorationEnabled != userState.isTouchExplorationEnabledLocked()) {
             userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
-            userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
             final long identity = Binder.clearCallingIdentity();
             try {
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
@@ -1908,6 +1912,8 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
+        userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
+        userState.setMultiFingerGesturesLocked(requestMultiFingerGestures);
     }
 
     private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 49b1158..4e7da97 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -104,6 +104,7 @@
     private boolean mIsTextHighContrastEnabled;
     private boolean mIsTouchExplorationEnabled;
     private boolean mServiceHandlesDoubleTap;
+    private boolean mRequestMultiFingerGestures;
     private int mUserInteractiveUiTimeout;
     private int mUserNonInteractiveUiTimeout;
     private int mNonInteractiveUiTimeout = 0;
@@ -153,6 +154,7 @@
         mAccessibilityButtonTargets.clear();
         mIsTouchExplorationEnabled = false;
         mServiceHandlesDoubleTap = false;
+        mRequestMultiFingerGestures = false;
         mIsDisplayMagnificationEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
@@ -354,6 +356,7 @@
         if (a11yEnabled && mIsTouchExplorationEnabled) {
             clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
             clientState |= AccessibilityManager.STATE_FLAG_DISPATCH_DOUBLE_TAP;
+            clientState |= AccessibilityManager.STATE_FLAG_REQUEST_MULTI_FINGER_GESTURES;
         }
         if (mIsTextHighContrastEnabled) {
             clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
@@ -436,6 +439,8 @@
         pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
         pw.append(", serviceHandlesDoubleTap=")
                 .append(String.valueOf(mServiceHandlesDoubleTap));
+        pw.append(", requestMultiFingerGestures=")
+                .append(String.valueOf(mRequestMultiFingerGestures));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
                 mIsDisplayMagnificationEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
@@ -688,6 +693,14 @@
         mServiceHandlesDoubleTap = enabled;
     }
 
+    public boolean isMultiFingerGesturesEnabledLocked() {
+        return mRequestMultiFingerGestures;
+    }
+
+    public void setMultiFingerGesturesLocked(boolean enabled) {
+        mRequestMultiFingerGestures = enabled;
+    }
+
     public int getUserInteractiveUiTimeoutLocked() {
         return mUserInteractiveUiTimeout;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 9afcf8d..b5660ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -65,8 +65,13 @@
     private final Handler mHandler;
     // Listener to be notified of gesture start and end.
     private Listener mListener;
-    // Whether enhanced touch exploration mode is enabled.
+    // Whether double tap and double tap and hold will be dispatched to the service or handled in
+    // the framework.
     private boolean mServiceHandlesDoubleTap = false;
+    // Whether multi-finger gestures are enabled.
+    boolean mMultiFingerGesturesEnabled;
+    // A list of all the multi-finger gestures, for easy adding and removal.
+    private final List<GestureMatcher> mMultiFingerGestures = new ArrayList<>();
     // Shared state information.
     private TouchState mState;
 
@@ -257,11 +262,26 @@
         }
     }
 
-    public boolean isServiceHandlesDoubleTapEnabled() {
-        return mServiceHandlesDoubleTap;
+    public boolean isMultiFingerGesturesEnabled() {
+        return mMultiFingerGesturesEnabled;
+    }
+
+    public void setMultiFingerGesturesEnabled(boolean mode) {
+        if (mMultiFingerGesturesEnabled != mode) {
+            mMultiFingerGesturesEnabled = mode;
+            if (mode) {
+                mGestures.addAll(mMultiFingerGestures);
+            } else {
+                mGestures.removeAll(mMultiFingerGestures);
+            }
+        }
     }
 
     public void setServiceHandlesDoubleTap(boolean mode) {
         mServiceHandlesDoubleTap = mode;
     }
+
+    public boolean isServiceHandlesDoubleTapEnabled() {
+        return mServiceHandlesDoubleTap;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index caf1424..2cc11c5 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -515,6 +515,9 @@
                 }
                 break;
             case 2:
+                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                    return;
+                }
                 // Make sure we don't have any pending transitions to touch exploration
                 mSendHoverEnterAndMoveDelayed.cancel();
                 mSendHoverExitDelayed.cancel();
@@ -537,6 +540,9 @@
                 }
                 break;
             default:
+                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                    return;
+                }
                 // More than two pointers are delegated to the view hierarchy.
                 mState.startDelegating();
                 event = MotionEvent.obtainNoHistory(event);
@@ -582,6 +588,9 @@
                         event, MotionEvent.ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
                 break;
             case 2:
+                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                    return;
+                }
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
                     // We have not started sending events so cancel
                     // scheduled sending events.
@@ -609,6 +618,9 @@
                 }
                 break;
             default:
+                if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+                    return;
+                }
                 // Three or more fingers is  something other than touch exploration.
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
                     // We have not started sending events so cancel
@@ -631,6 +643,10 @@
      */
     private void handleMotionEventStateDragging(
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+            // Multi-finger gestures conflict with this functionality.
+            return;
+        }
         int pointerIdBits = 0;
         // Clear the dragging pointer id if it's no longer valid.
         if (event.findPointerIndex(mDraggingPointerId) == -1) {
@@ -741,6 +757,10 @@
      */
     private void handleMotionEventStateDelegating(
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mGestureDetector.isMultiFingerGesturesEnabled()) {
+            // Multi-finger gestures conflict with this functionality.
+            return;
+        }
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 Slog.e(LOG_TAG, "Delegating state can only be reached if "
@@ -882,6 +902,14 @@
     }
 
     /**
+     * This function turns on and off multi-finger gestures. When enabled, multi-finger gestures
+     * will disable delegating and dragging functionality.
+     */
+    public void setMultiFingerGesturesEnabled(boolean enabled) {
+        mGestureDetector.setMultiFingerGesturesEnabled(enabled);
+    }
+
+    /**
      * Class for delayed exiting from gesture detecting mode.
      */
     private final class ExitGestureDetectionModeDelayed implements Runnable {