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

Commit 63e6a355 authored by Garfield Tan's avatar Garfield Tan
Browse files

Dispatch mouse events to the view under cursor.

Events from touchpads may have multiple fingers on them, and the down
event may be active with a finger which coordinate isn't in the view
where the mouse cursor is. Need to make sure it's dispatched to the view
under the mouse cursor.

Bug: 140312476
Test: Two-finger scroll on DualShock 4 always happen in the view under
the cursor, regardless of the order of finger put on the touch pad.
Test: atest FrameworksCoreTests:ViewGroupTest

Change-Id: I679a0a38c830bd418bb412bc6f0d78ba5c93224a
parent f90c2775
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -2634,8 +2634,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
+67 −4
Original line number Diff line number Diff line
@@ -16,9 +16,14 @@

package android.view;

import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.graphics.Region;
@@ -39,6 +44,60 @@ import org.junit.Test;
@SmallTest
public class ViewGroupTest {

    @Test
    public void testDispatchMouseEventsUnderCursor() {
        final Context context = getInstrumentation().getContext();
        final TestView viewGroup = new TestView(context, 0 /* left */, 0 /* top */,
                200 /* right */, 200 /* bottom */);
        final TestView viewA = spy(new TestView(context, 0 /* left */, 0 /* top */,
                100 /* right */, 200 /* bottom */));
        final TestView viewB = spy(new TestView(context, 100 /* left */, 0 /* top */,
                200 /* right */, 200 /* bottom */));

        viewGroup.addView(viewA);
        viewGroup.addView(viewB);

        // Make sure all of them handle touch events dispatched to them.
        doReturn(true).when(viewA).dispatchTouchEvent(any());
        doReturn(true).when(viewB).dispatchTouchEvent(any());

        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[2];
        properties[0] = new MotionEvent.PointerProperties();
        properties[0].id = 0;
        properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
        properties[1] = new MotionEvent.PointerProperties();
        properties[1].id = 1;
        properties[1].toolType = MotionEvent.TOOL_TYPE_FINGER;

        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[2];
        coords[0] = new MotionEvent.PointerCoords();
        coords[0].x = 80;
        coords[0].y = 100;
        coords[1] = new MotionEvent.PointerCoords();
        coords[1].x = 240;
        coords[1].y = 100;

        MotionEvent event;
        // Make sure the down event is active with a pointer which coordinate is different from the
        // cursor position, which is the midpoint of all 2 pointers above.
        event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, MotionEvent.ACTION_DOWN,
                2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */,
                0 /* xPrecision */, 0 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */,
                InputDevice.SOURCE_MOUSE, 0 /* flags */);
        viewGroup.dispatchTouchEvent(event);
        verify(viewB).dispatchTouchEvent(event);

        event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
                MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
                2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */,
                0 /* xPrecision */, 0 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */,
                InputDevice.SOURCE_MOUSE, 0 /* flags */);
        viewGroup.dispatchTouchEvent(event);
        verify(viewB).dispatchTouchEvent(event);

        verify(viewA, never()).dispatchTouchEvent(any());
    }

    /**
     * Test if {@link ViewGroup#subtractObscuredTouchableRegion} works as expected.
     *
@@ -59,7 +118,7 @@ public class ViewGroupTest {
     */
    @Test
    public void testSubtractObscuredTouchableRegion() {
        final Context context = getContext();
        final Context context = getInstrumentation().getContext();
        final TestView viewA = new TestView(context, 8 /* right */);
        final TestView viewB = new TestView(context, 6 /* right */);
        final TestView viewC = new TestView(context, 10 /* right */);
@@ -119,10 +178,14 @@ public class ViewGroupTest {
                (contain ? "" : " not"), x), contain, region.contains(x, 0 /* y */));
    }

    private static class TestView extends ViewGroup {
    public static class TestView extends ViewGroup {
        TestView(Context context, int right) {
            this(context, 0 /* left */, 0 /* top */, right, 1 /* bottom */);
        }

        TestView(Context context, int left, int top, int right, int bottom) {
            super(context);
            setFrame(0 /* left */, 0 /* top */, right, 1 /* bottom */);
            setFrame(left, top, right, bottom);
        }

        @Override