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

Commit 5dcb907b authored by Jean Chen's avatar Jean Chen
Browse files

feat(MultiFingerMultiTap): Implement two finger triple tap feature on...

feat(MultiFingerMultiTap): Implement two finger triple tap feature on FullScreenMagnificationGestureHandler

NO_IFTTT=add the multi-finger multi-tap feature without syncing to the old state

Bug: 297805269
Test: manual
Test: adb shell device_config put accessibility com.android.server.accessibility.enable_magnification_multiple_finger_multiple_tap_gesture true
adb shell stop && adb shell start
atest FullScreenMagnificationGestureHandlerTest

Change-Id: Ib2ed22edf47673aa43eab758aaebe69f2020c349
parent cc19bc2c
Loading
Loading
Loading
Loading
+71 −8
Original line number Diff line number Diff line
@@ -651,7 +651,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                }
                break;
                case ACTION_MOVE: {
                    if (event.getPointerCount() != 1) {
                    if (event.getPointerCount() > 2) {
                        throw new GestureException("Should have one pointer down.");
                    }
                    final float eventX = event.getX();
@@ -686,8 +686,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                }
                    break;

                case ACTION_DOWN:
                case ACTION_POINTER_UP: {
                case ACTION_DOWN: {
                    throw new GestureException(
                            "Unexpected event type: " + MotionEvent.actionToString(action));
                }
@@ -856,6 +855,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
    }

    final class DetectingStateWithMultiFinger extends DetectingState {
        // A flag set to true when two fingers have touched down.
        // Used to indicate what next finger action should be.
        private boolean mIsTwoFingerCountReached = false;
        // A tap counts when two fingers are down and up once.
        private int mCompletedTapCount = 0;
        DetectingStateWithMultiFinger(Context context) {
            super(context);
        }
@@ -886,6 +890,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                        transitionToDelegatingStateAndClear();

                    } else if (mDetectSingleFingerTripleTap
                            || mDetectTwoFingerTripleTap
                            // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay
                            // to ensure reachability of
                            // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
@@ -901,18 +906,36 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                }
                break;
                case ACTION_POINTER_DOWN: {
                    mIsTwoFingerCountReached = mDetectTwoFingerTripleTap
                            && event.getPointerCount() == 2;
                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);

                    if (isActivated() && event.getPointerCount() == 2) {
                        storePointerDownLocation(mSecondPointerDownLocation, event);
                        mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
                                ViewConfiguration.getTapTimeout());
                    } else if (mIsTwoFingerCountReached) {
                        // Placing two-finger triple-taps behind isActivated to avoid
                        // blocking panning scaling state
                        if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) {
                            // 3tap and hold
                            afterLongTapTimeoutTransitionToDraggingState(event);
                        } else {
                            afterMultiTapTimeoutTransitionToDelegatingState();
                        }
                    } else {
                        transitionToDelegatingStateAndClear();
                    }
                }
                break;
                case ACTION_POINTER_UP: {
                    // If it is a two-finger gesture, do not transition to the delegating state
                    // to ensure the reachability of
                    // the two-finger triple tap (triggerable with ACTION_MOVE and ACTION_UP)
                    if (!mIsTwoFingerCountReached) {
                        transitionToDelegatingStateAndClear();
                    }
                }
                break;
                case ACTION_MOVE: {
                    if (isFingerDown()
@@ -932,6 +955,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                            }
                            //Primary pointer is swiping, so transit to PanningScalingState
                            transitToPanningScalingStateAndClear();
                        } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)
                                && event.getPointerCount() == 2) {
                            // Placing two-finger triple-taps behind isActivated to avoid
                            // blocking panning scaling state
                            transitionToViewportDraggingStateAndClear(event);
                        } else if (mIsSinglePanningEnabled
                                && isActivated()
                                && event.getPointerCount() == 1) {
@@ -962,12 +990,18 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                    } else if (isMultiTapTriggered(3 /* taps */)) {
                        onTripleTap(/* up */ event);

                    } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) {
                        onTripleTap(event);

                    } else if (
                            // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
                            isFingerDown()
                            //TODO long tap should never happen here
                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) {
                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))
                            // If it is a two-finger but not reach 3 tap, do not transition to the
                            // delegating state to ensure the reachability of the triple tap
                            && mCompletedTapCount == 0) {
                        transitionToDelegatingStateAndClear();

                    }
@@ -976,6 +1010,33 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            }
        }
        // LINT.ThenChange(:detecting_state)

        @Override
        public void clear() {
            mCompletedTapCount = 0;
            setShortcutTriggered(false);
            removePendingDelayedMessages();
            clearDelayedMotionEvents();
            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
        }

        private boolean isMultiFingerMultiTapTriggered(int targetTapCount, MotionEvent event) {
            if (event.getActionMasked() == ACTION_UP && mIsTwoFingerCountReached) {
                mCompletedTapCount++;
                mIsTwoFingerCountReached = false;
            }
            return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount;
        }

        void transitionToDelegatingStateAndClear() {
            mCompletedTapCount = 0;
            transitionTo(mDelegatingState);
            sendDelayedMotionEvents();
            removePendingDelayedMessages();
            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
        }
    }

    /**
@@ -1355,6 +1416,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

            // Only log the 3tap and hold event
            if (!shortcutTriggered) {
                // TODO:(b/309534286): Add metrics for two-finger triple-tap and fix
                //  the log two-finger bug before enabling the flag
                // Triple tap and hold also belongs to triple tap event
                final boolean enabled = !isActivated();
                mMagnificationLogger.logMagnificationTripleTap(enabled);
+93 −0
Original line number Diff line number Diff line
@@ -55,6 +55,10 @@ import android.os.Message;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableContext;
import android.util.DebugUtils;
@@ -69,6 +73,7 @@ import com.android.internal.util.ConcurrentUtils;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
@@ -134,6 +139,9 @@ import java.util.function.IntConsumer;
@RunWith(AndroidJUnit4.class)
public class FullScreenMagnificationGestureHandlerTest {

    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    public static final int STATE_IDLE = 1;
    public static final int STATE_ACTIVATED = 2;
    public static final int STATE_SHORTCUT_TRIGGERED = 3;
@@ -425,6 +433,7 @@ public class FullScreenMagnificationGestureHandlerTest {
    }

    @Test
    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
    public void testDisablingTripleTap_removesInputLag() {
        mMgh = newInstance(/* detectSingleFingerTripleTap */ false,
                /* detectTwoFingerTripleTap */ true, /* detectShortcut */ true);
@@ -435,6 +444,18 @@ public class FullScreenMagnificationGestureHandlerTest {
        verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt());
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
    public void testDisablingSingleFingerTripleTapAndTwoFingerTripleTap_removesInputLag() {
        mMgh = newInstance(/* detectSingleFingerTripleTap */ false,
                /* detectTwoFingerTripleTap */ false, /* detectShortcut */ true);
        goFromStateIdleTo(STATE_IDLE);
        allowEventDelegation();
        tap();
        // no fast forward
        verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt());
    }

    @Test
    public void testLongTapAfterShortcutTriggered_neverLogMagnificationTripleTap() {
        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
@@ -509,6 +530,54 @@ public class FullScreenMagnificationGestureHandlerTest {
                STATE_ZOOMED_WITH_PERSISTED_SCALE_TMP);
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
    public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() {
        goFromStateIdleTo(STATE_IDLE);

        twoFingerTap();
        twoFingerTap();
        twoFingerTap();

        assertIn(STATE_ACTIVATED);
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
    public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() {
        goFromStateIdleTo(STATE_ACTIVATED);

        twoFingerTap();
        twoFingerTap();
        twoFingerTap();

        assertIn(STATE_IDLE);
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
    public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() {
        goFromStateIdleTo(STATE_IDLE);

        twoFingerTap();
        twoFingerTap();
        twoFingerTapAndHold();

        assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
    public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() {
        goFromStateIdleTo(STATE_IDLE);

        twoFingerTap();
        twoFingerTap();
        twoFingerSwipeAndHold();

        assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP);
    }

    @Test
    public void testMultiTap_outOfDistanceSlop_shouldInIdle() {
        // All delay motion events should be sent, if multi-tap with out of distance slop.
@@ -1258,6 +1327,30 @@ public class FullScreenMagnificationGestureHandlerTest {
        send(upEvent());
    }

    private void twoFingerTap() {
        send(downEvent());
        send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
        send(pointerEvent(ACTION_POINTER_UP, DEFAULT_X * 2, DEFAULT_Y));
        send(upEvent());
    }

    private void twoFingerTapAndHold() {
        send(downEvent());
        send(pointerEvent(ACTION_POINTER_DOWN, DEFAULT_X * 2, DEFAULT_Y));
        fastForward(2000);
    }

    private void twoFingerSwipeAndHold() {
        PointF pointer1 = DEFAULT_POINT;
        PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);

        send(downEvent());
        send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
        final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
        pointer1.offset(sWipeMinDistance + 1, 0);
        send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
    }

    private void triggerShortcut() {
        mMgh.notifyShortcutTriggered();
    }