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

Commit 567c01e1 authored by Hiroki Sato's avatar Hiroki Sato
Browse files

Allow moving WindowMagnification with a mouse even at the edge of a screen

When moving a window magnification with a mouse, it may be stack at the
edge of the screen, and a user have to perform drag move again to
actually magnify the edge of the screen.

With mouse input devices, even if the cursor is at the edge of the
screen, actual delta that the input device is emitting can be obtained.
We can use the value to move the magnification window so that users with
a mouse can smoothly magnify the entire screen.

Bug: 420776720
Test: MagnificationGestureDetectorTest
Flag: com.android.systemui.window_magnification_move_with_mouse_on_edge
Change-Id: I801f7994a9d360797d01b87df3199f8a14b67dec
parent ff0e10f2
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -108,3 +108,13 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "window_magnification_move_with_mouse_on_edge"
    namespace: "accessibility"
    description: "Allows window magnification to be moved by mouse further on the edge of the screen."
    bug: "420776720"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+72 −2
Original line number Diff line number Diff line
@@ -26,13 +26,17 @@ import static org.mockito.Mockito.verify;

import android.os.Handler;
import android.os.SystemClock;
import android.platform.test.annotations.EnableFlags;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import androidx.test.core.view.PointerCoordsBuilder;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;

import org.junit.After;
@@ -50,9 +54,9 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {

    private static final float ACTION_DOWN_X = 100;
    private static final float ACTION_DOWN_Y = 200;
    private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    private MagnificationGestureDetector mGestureDetector;
    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
    private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
    private View mSpyView;
    @Mock
    private MagnificationGestureDetector.OnGestureListener mListener;
@@ -133,6 +137,37 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
        verify(mListener, never()).onSingleTap(mSpyView);
    }

    @Test
    public void performSingleTapWithBatchedSmallTouchEvents_invokeCallback() {
        mGestureDetector = new MagnificationGestureDetector(mContext, mHandler, mListener);

        final long downTime = SystemClock.uptimeMillis();
        final MotionEvent.PointerCoords coords = PointerCoordsBuilder.newBuilder()
                .setCoords(ACTION_DOWN_X, ACTION_DOWN_Y).build();

        final MotionEvent downEvent = mMotionEventHelper.obtainBatchedMotionEvent(downTime,
                MotionEvent.ACTION_DOWN, InputDevice.SOURCE_TOUCHSCREEN,
                MotionEvent.TOOL_TYPE_FINGER, coords);

        // Move event without changing location.
        final MotionEvent.PointerCoords dragCoords1 = PointerCoordsBuilder.newBuilder()
                .setCoords(ACTION_DOWN_X + mTouchSlop / 2.f, ACTION_DOWN_Y).build();
        final MotionEvent.PointerCoords dragCoords2 = new MotionEvent.PointerCoords(dragCoords1);
        final MotionEvent moveEvent = mMotionEventHelper.obtainBatchedMotionEvent(downTime,
                MotionEvent.ACTION_MOVE, InputDevice.SOURCE_TOUCHSCREEN,
                MotionEvent.TOOL_TYPE_FINGER, dragCoords1, dragCoords2);

        final MotionEvent upEvent = mMotionEventHelper.obtainBatchedMotionEvent(downTime,
                MotionEvent.ACTION_UP, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.TOOL_TYPE_FINGER,
                coords);

        mGestureDetector.onTouch(mSpyView, downEvent);
        mGestureDetector.onTouch(mSpyView, moveEvent);
        mGestureDetector.onTouch(mSpyView, upEvent);

        verify(mListener).onSingleTap(mSpyView);
    }

    @Test
    public void performLongPress_invokeCallbacksInOrder() {
        final long downTime = SystemClock.uptimeMillis();
@@ -173,4 +208,39 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
        inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
        verify(mListener, never()).onSingleTap(mSpyView);
    }

    @Test
    @EnableFlags(Flags.FLAG_WINDOW_MAGNIFICATION_MOVE_WITH_MOUSE_ON_EDGE)
    public void performDragWithMouse_invokeCallbacksUsingRelative() {
        mGestureDetector = new MagnificationGestureDetector(mContext, mHandler, mListener);

        final long downTime = SystemClock.uptimeMillis();
        final MotionEvent.PointerCoords coords = PointerCoordsBuilder.newBuilder()
                .setCoords(ACTION_DOWN_X, ACTION_DOWN_Y).build();

        final MotionEvent downEvent = mMotionEventHelper.obtainBatchedMotionEvent(downTime,
                MotionEvent.ACTION_DOWN, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE,
                coords);

        // Drag event without changing location but with relative delta on X axis.
        final MotionEvent.PointerCoords dragCoords1 = new MotionEvent.PointerCoords(coords);
        dragCoords1.setAxisValue(MotionEvent.AXIS_RELATIVE_X, mTouchSlop + 10);
        final MotionEvent.PointerCoords dragCoords2 = new MotionEvent.PointerCoords(coords);
        dragCoords2.setAxisValue(MotionEvent.AXIS_RELATIVE_X, 20);
        final MotionEvent moveEvent = mMotionEventHelper.obtainBatchedMotionEvent(downTime,
                MotionEvent.ACTION_MOVE, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE,
                dragCoords1, dragCoords2);

        final MotionEvent upEvent = mMotionEventHelper.obtainBatchedMotionEvent(downTime,
                MotionEvent.ACTION_UP, InputDevice.SOURCE_MOUSE, MotionEvent.TOOL_TYPE_MOUSE,
                coords);

        mGestureDetector.onTouch(mSpyView, downEvent);
        mGestureDetector.onTouch(mSpyView, moveEvent);
        mGestureDetector.onTouch(mSpyView, upEvent);

        verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
        verify(mListener).onDrag(mSpyView, mTouchSlop + 30, 0);
        verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
    }
}
+23 −2
Original line number Diff line number Diff line
@@ -22,10 +22,13 @@ import android.content.Context;
import android.graphics.PointF;
import android.os.Handler;
import android.view.Display;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.android.systemui.Flags;

/**
 * Detects single tap and drag gestures using the supplied {@link MotionEvent}s. The {@link
 * OnGestureListener} callback will notify users when a particular motion event has occurred. This
@@ -194,8 +197,22 @@ class MagnificationGestureDetector {
                    break;
                case MotionEvent.ACTION_MOVE:
                case MotionEvent.ACTION_UP:
                    float dx = event.getRawX() - mLastLocation.x;
                    float dy = event.getRawY() - mLastLocation.y;
                    float dx = 0;
                    float dy = 0;
                    if (Flags.windowMagnificationMoveWithMouseOnEdge() && isMouseEvent(event)) {
                        // With mouse input, we use relative delta values so that user can drag
                        // even at the edge of the screen, where the pointer location doesn't change
                        // but input event still contain the delta value.
                        for (int i = 0; i < event.getHistorySize(); i++) {
                            dx += event.getHistoricalAxisValue(MotionEvent.AXIS_RELATIVE_X, i);
                            dy += event.getHistoricalAxisValue(MotionEvent.AXIS_RELATIVE_Y, i);
                        }
                        dx += event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
                        dy += event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
                    } else {
                        dx = event.getRawX() - mLastLocation.x;
                        dy = event.getRawY() - mLastLocation.y;
                    }
                    mAccumulatedDelta.offset(dx, dy);
                    mLastLocation.set(event.getRawX(), event.getRawY());
                    break;
@@ -227,5 +244,9 @@ class MagnificationGestureDetector {
            pointF.x = Float.NaN;
            pointF.y = Float.NaN;
        }

        private static boolean isMouseEvent(MotionEvent event) {
            return (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE;
        }
    }
}
+31 −0
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ package com.android.systemui.accessibility;

import android.view.MotionEvent;

import androidx.test.core.view.MotionEventBuilder;
import androidx.test.core.view.PointerPropertiesBuilder;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
@@ -44,4 +47,32 @@ public class MotionEventHelper {
        }
        return event;
    }

    /**
     * Creates a {@link MotionEvent} with a batch of pointers. Call this with one or more pointer
     * coordinates, which will be added to the batch of the event.
     */
    public MotionEvent obtainBatchedMotionEvent(long downTime, int action, int source,
            int toolType, MotionEvent.PointerCoords... coords) {
        if (coords.length == 0) {
            throw new IllegalArgumentException("coords cannot be empty");
        }
        MotionEvent event = MotionEventBuilder.newBuilder()
                .setDownTime(downTime)
                .setEventTime(downTime)
                .setAction(action)
                .setSource(source)
                .setPointer(
                        PointerPropertiesBuilder.newBuilder().setToolType(toolType).build(),
                        coords[0])
                .build();
        for (int i = 1; i < coords.length; i++) {
            event.addBatch(downTime, new MotionEvent.PointerCoords[]{coords[i]}, /* metaState= */
                    0);
        }
        synchronized (this) {
            mMotionEvents.add(event);
        }
        return event;
    }
}