Add multi-finger double tap and hold gestures.

This CL includes two, three and four finger double-tap and hold.

Bug: 136131815
Test: atest GestureManifoldTest TouchExplorerTest AccessibilityGestureDetectorTest
Change-Id: I4c0a95a4ac2d13e0a7e2c7920df619063ec1cfc0
diff --git a/api/current.txt b/api/current.txt
index 4887c66..6fe737c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2876,6 +2876,7 @@
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
     method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>);
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
+    field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
     field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
     field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
@@ -2883,6 +2884,7 @@
     field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19
     field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
+    field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
     field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
     field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
     field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
@@ -2890,6 +2892,7 @@
     field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
     field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
     field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26
+    field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a
     field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
     field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22
     field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index ace13513..25729ab 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -18,6 +18,7 @@
 
 
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
@@ -25,6 +26,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
@@ -32,6 +34,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
@@ -83,9 +86,11 @@
     @IntDef(prefix = { "GESTURE_" }, value = {
             GESTURE_2_FINGER_SINGLE_TAP,
             GESTURE_2_FINGER_DOUBLE_TAP,
+            GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_TRIPLE_TAP,
             GESTURE_3_FINGER_SINGLE_TAP,
             GESTURE_3_FINGER_DOUBLE_TAP,
+            GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_TRIPLE_TAP,
             GESTURE_DOUBLE_TAP,
             GESTURE_DOUBLE_TAP_AND_HOLD,
@@ -114,6 +119,7 @@
             GESTURE_3_FINGER_SWIPE_RIGHT,
             GESTURE_3_FINGER_SWIPE_UP,
             GESTURE_4_FINGER_DOUBLE_TAP,
+            GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_4_FINGER_SINGLE_TAP,
             GESTURE_4_FINGER_SWIPE_DOWN,
             GESTURE_4_FINGER_SWIPE_LEFT,
@@ -175,12 +181,18 @@
         switch (eventType) {
             case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
             case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
+            case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
             case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
             case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
+            case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
             case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
             case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
+            case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP";
             case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP";
             case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD";
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index b65f68e..0ed6b1f 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -411,6 +411,15 @@
     /** The user has performed a four-finger triple tap gesture on the touch screen. */
     public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39;
 
+    /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40;
+
+    /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
+
+    /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
+
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
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 b74be7e..a3b5a3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility.gestures;
 
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
@@ -24,6 +25,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
@@ -31,6 +33,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
@@ -132,6 +135,9 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this));
         // Three-finger taps.
         mMultiFingerGestures.add(
@@ -139,6 +145,9 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
         // Four-finger taps.
         mMultiFingerGestures.add(
@@ -146,6 +155,9 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this));
         // Two-finger swipes.
         mMultiFingerGestures.add(
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
index 20def61..e5340f1 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
@@ -42,10 +42,10 @@
     // The acceptable distance the pointer can move and still count as a tap.
     private int mTouchSlop;
     // A tap counts when target number of fingers are down and up once.
-    private int mCompletedTapCount;
+    protected int mCompletedTapCount;
     // A flag set to true when target number of fingers have touched down at once before.
     // Used to indicate what next finger action should be. Down when false and lift when true.
-    private boolean mIsTargetFingerCountReached = false;
+    protected boolean mIsTargetFingerCountReached = false;
     // Store initial down points for slop checking and update when next down if is inside slop.
     private PointF[] mBases;
     // The points in bases that already have slop checked when onDown or onPointerDown.
@@ -56,7 +56,11 @@
      * @throws IllegalArgumentException if <code>fingers<code/> is less than 2
      *                                  or <code>taps<code/> is not positive.
      */
-    MultiFingerMultiTap(Context context, int fingers, int taps, int gestureId,
+    MultiFingerMultiTap(
+            Context context,
+            int fingers,
+            int taps,
+            int gestureId,
             GestureMatcher.StateChangeListener listener) {
         super(gestureId, new Handler(context.getMainLooper()), listener);
         Preconditions.checkArgument(fingers >= 2);
@@ -117,8 +121,7 @@
         cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
 
         final PointF nearest = findNearestPoint(rawEvent, mTouchSlop, false);
-        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR)
-                && null != nearest) {
+        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && null != nearest) {
             // Increase current tap count when the user have all fingers lifted
             // within the tap timeout since the target number of fingers are down.
             if (mIsTargetFingerCountReached) {
@@ -169,8 +172,7 @@
         } else {
             nearest = findNearestPoint(rawEvent, mDoubleTapSlop, true);
         }
-        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR)
-                && nearest != null) {
+        if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && nearest != null) {
             // The user have all fingers down within the tap timeout since first finger down,
             // setting the timeout for fingers to be lifted.
             if (currentFingerCount == mTargetFingerCount) {
@@ -227,11 +229,11 @@
     }
 
     /**
-     * Find the nearest location to the given event in the bases.
-     * If no one found, it could be not inside {@code slop}, filtered or empty bases.
-     * When {@code filterMatched} is true, if the location of given event matches one of the points
-     * in {@link #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location
-     * will be added to {@link #mExcludedPointsForDownSlopChecked}.
+     * Find the nearest location to the given event in the bases. If no one found, it could be not
+     * inside {@code slop}, filtered or empty bases. When {@code filterMatched} is true, if the
+     * location of given event matches one of the points in {@link
+     * #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location will be
+     * added to {@link #mExcludedPointsForDownSlopChecked}.
      *
      * @param event to find nearest point in bases.
      * @param slop to check to the given location of the event.
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
new file mode 100644
index 0000000..7824fd9
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-finger multi-tap and hold. The number of fingers
+ * and taps for each instance is specified in the constructor.
+ */
+class MultiFingerMultiTapAndHold extends MultiFingerMultiTap {
+
+    MultiFingerMultiTapAndHold(
+            Context context,
+            int fingers,
+            int taps,
+            int gestureId,
+            GestureMatcher.StateChangeListener listener) {
+        super(context, fingers, taps, gestureId, listener);
+    }
+
+    @Override
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        super.onPointerDown(event, rawEvent, policyFlags);
+        if (mIsTargetFingerCountReached && mCompletedTapCount + 1 == mTargetTapCount) {
+            completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mCompletedTapCount + 1 == mTargetFingerCount) {
+            // Calling super.onUp  would complete the multi-tap version of this.
+            cancelGesture(event, rawEvent, policyFlags);
+        } else {
+            super.onUp(event, rawEvent, policyFlags);
+            cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    public String getGestureName() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(mTargetFingerCount).append("-Finger ");
+        if (mTargetTapCount == 1) {
+            builder.append("Single");
+        } else if (mTargetTapCount == 2) {
+            builder.append("Double");
+        } else if (mTargetTapCount == 3) {
+            builder.append("Triple");
+        } else if (mTargetTapCount > 3) {
+            builder.append(mTargetTapCount);
+        }
+        return builder.append(" Tap and hold").toString();
+    }
+}