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

Commit da65cb2b authored by Riley Jones's avatar Riley Jones
Browse files

Altering TouchExplorer behavior to support Split Tap

Allows touchExplorer to broadcast a POINTER_UP event while in the touch exploration state.
On TOUCH_INTERACTION_END, TouchState transitions to touch exploration instead of clear if there are any pointers down.

Bug: 374930391
Test: atest TouchExplorerTest TouchStateTest
Test: manually verify conditions mentioned in bug do not persist.
Flag: com.android.server.accessibility.pointer_up_motion_event_in_touch_exploration
Change-Id: I33b8d992c9e47d7bd62d2242b1ddbd52d6f81df1
parent f18caa57
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -259,6 +259,16 @@ flag {
    bug: "295327792"
}

flag {
    name: "pointer_up_motion_event_in_touch_exploration"
    namespace: "accessibility"
    description: "Allows POINTER_UP motionEvents to trigger during touch exploration."
    bug: "374930391"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "proxy_use_apps_on_virtual_device_listener"
    namespace: "accessibility"
+8 −0
Original line number Diff line number Diff line
@@ -653,6 +653,14 @@ public class TouchExplorer extends BaseEventStreamTransformation
            case ACTION_UP:
                handleActionUp(event, rawEvent, policyFlags);
                break;
            case ACTION_POINTER_UP:
                if (com.android.server.accessibility.Flags
                        .pointerUpMotionEventInTouchExploration()) {
                    if (mState.isServiceDetectingGestures()) {
                        mAms.sendMotionEventToListeningServices(rawEvent);
                    }
                }
                break;
            default:
                break;
        }
+19 −4
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.view.Display;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;

import androidx.annotation.VisibleForTesting;

import com.android.server.accessibility.AccessibilityManagerService;

/**
@@ -73,7 +75,8 @@ public class TouchState {
    private int mState = STATE_CLEAR;
    // Helper class to track received pointers.
    // Todo: collapse or hide this class so multiple classes don't modify it.
    private final ReceivedPointerTracker mReceivedPointerTracker;
    @VisibleForTesting
    public final ReceivedPointerTracker mReceivedPointerTracker;
    // The most recently received motion event.
    private MotionEvent mLastReceivedEvent;
    // The accompanying raw event without any transformations.
@@ -219,8 +222,19 @@ public class TouchState {
                startTouchInteracting();
                break;
            case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
                // When interaction ends, check if there are still down pointers.
                // If there are any down pointers, go directly to TouchExploring instead.
                if (com.android.server.accessibility.Flags
                        .pointerUpMotionEventInTouchExploration()) {
                    if (mReceivedPointerTracker.mReceivedPointersDown > 0) {
                        startTouchExploring();
                    } else {
                        setState(STATE_CLEAR);
                        // We will clear when we actually handle the next ACTION_DOWN.
                    }
                } else {
                    setState(STATE_CLEAR);
                }
                break;
            case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
                startTouchExploring();
@@ -419,7 +433,8 @@ public class TouchState {
        private final PointerDownInfo[] mReceivedPointers = new PointerDownInfo[MAX_POINTER_COUNT];

        // Which pointers are down.
        private int mReceivedPointersDown;
        @VisibleForTesting
        public int mReceivedPointersDown;

        // The edge flags of the last received down event.
        private int mLastReceivedDownEdgeFlags;
+32 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -46,6 +47,7 @@ import android.content.Context;
import android.graphics.PointF;
import android.os.Looper;
import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.DexmakerShareClassLoaderRule;
@@ -504,6 +506,36 @@ public class TouchExplorerTest {
        assertThat(sentRawEvent.getDisplayId()).isEqualTo(rawDisplayId);
    }

    @Test
    @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
    public void handleMotionEventStateTouchExploring_pointerUp_doesNotSendToManager() {
        mTouchExplorer.getState().setServiceDetectsGestures(true);
        mTouchExplorer.getState().clear();

        mLastEvent = pointerDownEvent();
        mTouchExplorer.getState().startTouchExploring();
        MotionEvent event = fromTouchscreen(pointerUpEvent());

        mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0);

        verify(mMockAms, never()).sendMotionEventToListeningServices(event);
    }

    @Test
    @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
    public void handleMotionEventStateTouchExploring_pointerUp_sendsToManager() {
        mTouchExplorer.getState().setServiceDetectsGestures(true);
        mTouchExplorer.getState().clear();

        mLastEvent = pointerDownEvent();
        mTouchExplorer.getState().startTouchExploring();
        MotionEvent event = fromTouchscreen(pointerUpEvent());

        mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0);

        verify(mMockAms).sendMotionEventToListeningServices(event);
    }

    /**
     * Used to play back event data of a gesture by parsing the log into MotionEvents and sending
     * them to TouchExplorer.
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;

import static com.android.server.accessibility.gestures.TouchState.STATE_CLEAR;
import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING;

import static com.google.common.truth.Truth.assertThat;

import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.Display;

import androidx.test.runner.AndroidJUnit4;

import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.Flags;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;

@RunWith(AndroidJUnit4.class)
public class TouchStateTest {
    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    private TouchState mTouchState;
    @Mock private AccessibilityManagerService mMockAms;

    @Before
    public void setup() {
        mTouchState = new TouchState(Display.DEFAULT_DISPLAY, mMockAms);
    }

    @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
    @Test
    public void injectedEvent_interactionEnd_pointerDown_startsTouchExploring() {
        mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 1;
        mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
        assertThat(mTouchState.getState()).isEqualTo(STATE_TOUCH_EXPLORING);
    }

    @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
    @Test
    public void injectedEvent_interactionEnd_pointerUp_clears() {
        mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 0;
        mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
        assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR);
    }

    @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION)
    @Test
    public void injectedEvent_interactionEnd_clears() {
        mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END);
        assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR);
    }
}