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

Commit 7155e937 authored by Kiara Patricia's avatar Kiara Patricia Committed by Android (Google) Code Review
Browse files

Merge "Create SinglePanningState that will handle single pointer/finger panning" into main

parents 29aeb06c 2d4f78e3
Loading
Loading
Loading
Loading
+106 −0
Original line number Diff line number Diff line
@@ -245,6 +245,31 @@ public class FullScreenMagnificationController implements
            return mCurrentMagnificationSpec.offsetY;
        }

        @GuardedBy("mLock")
        boolean isAtEdge() {
            return isAtLeftEdge() || isAtRightEdge() || isAtTopEdge() || isAtBottomEdge();
        }

        @GuardedBy("mLock")
        boolean isAtLeftEdge() {
            return getOffsetX() == getMaxOffsetXLocked();
        }

        @GuardedBy("mLock")
        boolean isAtRightEdge() {
            return getOffsetX() == getMinOffsetXLocked();
        }

        @GuardedBy("mLock")
        boolean isAtTopEdge() {
            return getOffsetY() == getMaxOffsetYLocked();
        }

        @GuardedBy("mLock")
        boolean isAtBottomEdge() {
            return getOffsetY() == getMinOffsetYLocked();
        }

        @GuardedBy("mLock")
        float getCenterX() {
            return (mMagnificationBounds.width() / 2.0f
@@ -1085,6 +1110,87 @@ public class FullScreenMagnificationController implements
        }
    }

    /**
     * Returns whether the user is at one of the edges (left, right, top, bottom)
     * of the magnification viewport
     *
     * @param displayId
     * @return if user is at the edge of the view
     */
    public boolean isAtEdge(int displayId) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return false;
            }
            return display.isAtEdge();
        }
    }

    /**
     * Returns whether the user is at the left edge of the viewport
     *
     * @param displayId
     * @return if user is at left edge of view
     */
    public boolean isAtLeftEdge(int displayId) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return false;
            }
            return display.isAtLeftEdge();
        }
    }

    /**
     * Returns whether the user is at the right edge of the viewport
     *
     * @param displayId
     * @return if user is at right edge of view
     */
    public boolean isAtRightEdge(int displayId) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return false;
            }
            return display.isAtRightEdge();
        }
    }

    /**
     * Returns whether the user is at the top edge of the viewport
     *
     * @param displayId
     * @return if user is at top edge of view
     */
    public boolean isAtTopEdge(int displayId) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return false;
            }
            return display.isAtTopEdge();
        }
    }

    /**
     * Returns whether the user is at the bottom edge of the viewport
     *
     * @param displayId
     * @return if user is at bottom edge of view
     */
    public boolean isAtBottomEdge(int displayId) {
        synchronized (mLock) {
            final DisplayMagnification display = mDisplays.get(displayId);
            if (display == null) {
                return false;
            }
            return display.isAtBottomEdge();
        }
    }

    /**
     * Returns the Y offset of the magnification viewport. If an animation
     * is in progress, this reflects the end state of the animation.
+107 −14
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
    @VisibleForTesting final DetectingState mDetectingState;
    @VisibleForTesting final PanningScalingState mPanningScalingState;
    @VisibleForTesting final ViewportDraggingState mViewportDraggingState;
    @VisibleForTesting final SinglePanningState mSinglePanningState;

    private final ScreenStateReceiver mScreenStateReceiver;
    private final WindowMagnificationPromptController mPromptController;
@@ -146,7 +147,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

    private PointerCoords[] mTempPointerCoords;
    private PointerProperties[] mTempPointerProperties;

    @VisibleForTesting boolean mIsSinglePanningEnabled;
    public FullScreenMagnificationGestureHandler(@UiContext Context context,
            FullScreenMagnificationController fullScreenMagnificationController,
            AccessibilityTraceManager trace,
@@ -202,6 +203,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
        mDetectingState = new DetectingState(context);
        mViewportDraggingState = new ViewportDraggingState();
        mPanningScalingState = new PanningScalingState(context);
        mSinglePanningState = new SinglePanningState(context);
        setSinglePanningEnabled(false);

        if (mDetectShortcutTrigger) {
            mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -213,6 +216,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
        transitionTo(mDetectingState);
    }

    @VisibleForTesting
    void setSinglePanningEnabled(boolean isEnabled) {
        mIsSinglePanningEnabled = isEnabled;
    }

    @Override
    void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        handleEventWith(mCurrentState, event, rawEvent, policyFlags);
@@ -223,6 +231,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
        // To keep InputEventConsistencyVerifiers within GestureDetectors happy
        mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
        mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
        mSinglePanningState.mScrollGestureDetector.onTouchEvent(event);

        try {
            stateHandler.onMotionEvent(event, rawEvent, policyFlags);
@@ -669,7 +678,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

        @Override
        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {

            // Ensures that the state at the end of delegation is consistent with the last delegated
            // UP/DOWN event in queue: still delegating if pointer is down, detecting otherwise
            switch (event.getActionMasked()) {
@@ -726,6 +734,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

        @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper(), this);

        private PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN);

        DetectingState(Context context) {
            mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
            mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
@@ -765,10 +775,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            cacheDelayedMotionEvent(event, rawEvent, policyFlags);
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN: {

                    mLastDetectingDownEventTime = event.getDownTime();
                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);

                    mFirstPointerDownLocation.set(event.getX(), event.getY());

                    if (!mFullScreenMagnificationController.magnificationRegionContains(
                            mDisplayId, event.getX(), event.getY())) {

@@ -800,7 +811,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                break;
                case ACTION_POINTER_DOWN: {
                    if (isActivated() && event.getPointerCount() == 2) {
                        storeSecondPointerDownLocation(event);
                        storePointerDownLocation(mSecondPointerDownLocation, event);
                        mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
                                ViewConfiguration.getTapTimeout());
                    } else {
@@ -815,7 +826,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                case ACTION_MOVE: {
                    if (isFingerDown()
                            && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {

                        // Swipe detected - transition immediately

                        // For convenience, viewport dragging takes precedence
@@ -826,10 +836,15 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                        } else if (isActivated() && event.getPointerCount() == 2) {
                            //Primary pointer is swiping, so transit to PanningScalingState
                            transitToPanningScalingStateAndClear();
                        } else if (mIsSinglePanningEnabled
                                && isActivated()
                                && event.getPointerCount() == 1
                                && !isOverscroll(event)) {
                            transitToSinglePanningStateAndClear();
                        } else {
                            transitionToDelegatingStateAndClear();
                        }
                    } else if (isActivated() && secondPointerDownValid()
                    } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation)
                            && distanceClosestPointerToPoint(
                            mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
                        //Second pointer is swiping, so transit to PanningScalingState
@@ -843,11 +858,9 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

                    if (!mFullScreenMagnificationController.magnificationRegionContains(
                            mDisplayId, event.getX(), event.getY())) {

                        transitionToDelegatingStateAndClear();

                    } else if (isMultiTapTriggered(3 /* taps */)) {

                        onTripleTap(/* up */ event);

                    } else if (
@@ -856,7 +869,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                            //TODO long tap should never happen here
                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) {

                        transitionToDelegatingStateAndClear();

                    }
@@ -865,14 +877,28 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            }
        }

        private void storeSecondPointerDownLocation(MotionEvent event) {
        private boolean isOverscroll(MotionEvent event) {
            if (!pointerDownValid(mFirstPointerDownLocation)) {
                return false;
            }
            float dX = event.getX() - mFirstPointerDownLocation.x;
            float dY = event.getY() - mFirstPointerDownLocation.y;
            boolean didOverscroll =
                    mFullScreenMagnificationController.isAtLeftEdge(mDisplayId) && dX > 0
                    || mFullScreenMagnificationController.isAtRightEdge(mDisplayId) && dX < 0
                    || mFullScreenMagnificationController.isAtTopEdge(mDisplayId) && dY > 0
                    || mFullScreenMagnificationController.isAtBottomEdge(mDisplayId) && dY < 0;
            return didOverscroll;
        }

        private void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) {
            final int index = event.getActionIndex();
            mSecondPointerDownLocation.set(event.getX(index), event.getY(index));
            pointerDownLocation.set(event.getX(index), event.getY(index));
        }

        private boolean secondPointerDownValid() {
            return !(Float.isNaN(mSecondPointerDownLocation.x) && Float.isNaN(
                    mSecondPointerDownLocation.y));
        private boolean pointerDownValid(PointF pointerDownLocation) {
            return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN(
                    pointerDownLocation.y));
        }

        private void transitToPanningScalingStateAndClear() {
@@ -880,6 +906,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            clear();
        }

        private void transitToSinglePanningStateAndClear() {
            transitionTo(mSinglePanningState);
            clear();
        }

        public boolean isMultiTapTriggered(int numTaps) {

            // Shortcut acts as the 2 initial taps
@@ -947,6 +978,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            setShortcutTriggered(false);
            removePendingDelayedMessages();
            clearDelayedMotionEvents();
            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
        }

@@ -1165,12 +1197,14 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                + ", mDelegatingState=" + mDelegatingState
                + ", mMagnifiedInteractionState=" + mPanningScalingState
                + ", mViewportDraggingState=" + mViewportDraggingState
                + ", mSinglePanningState=" + mSinglePanningState
                + ", mDetectTripleTap=" + mDetectTripleTap
                + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger
                + ", mCurrentState=" + State.nameOf(mCurrentState)
                + ", mPreviousState=" + State.nameOf(mPreviousState)
                + ", mMagnificationController=" + mFullScreenMagnificationController
                + ", mDisplayId=" + mDisplayId
                + ", mIsSinglePanningEnabled=" + mIsSinglePanningEnabled
                + '}';
    }

@@ -1285,8 +1319,67 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
     * Indicates an error with a gesture handler or state.
     */
    private static class GestureException extends Exception {

        GestureException(String message) {
            super(message);
        }
    }

    final class SinglePanningState extends SimpleOnGestureListener implements State {
        private final GestureDetector mScrollGestureDetector;
        private MotionEventInfo mEvent;

        SinglePanningState(Context context) {
            mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain());
        }

        @Override
        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
            int action = event.getActionMasked();
            switch (action) {
                case ACTION_UP:
                case ACTION_CANCEL:
                    clear();
                    transitionTo(mDetectingState);
                    break;
            }
        }

        @Override
        public boolean onScroll(
                MotionEvent first, MotionEvent second, float distanceX, float distanceY) {
            if (mCurrentState != mSinglePanningState) {
                return true;
            }
            mFullScreenMagnificationController.offsetMagnifiedRegion(
                    mDisplayId,
                    distanceX,
                    distanceY,
                    AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
            if (DEBUG_PANNING_SCALING) {
                Slog.i(
                        mLogTag,
                        "SinglePanningState Panned content by scrollX: "
                                + distanceX
                                + " scrollY: "
                                + distanceY
                                + " isAtEdge: "
                                + mFullScreenMagnificationController.isAtEdge(mDisplayId));
            }
            // TODO: b/280812104 Dispatch events before Delegation
            if (mFullScreenMagnificationController.isAtEdge(mDisplayId)) {
                clear();
                transitionTo(mDelegatingState);
            }
            return /* event consumed: */ true;
        }

        @Override
        public String toString() {
            return "SinglePanningState{"
                    + "isEdgeOfView="
                    + mFullScreenMagnificationController.isAtEdge(mDisplayId)
                    + "}";
        }
    }
}
+111 −5
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -42,6 +43,8 @@ import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
@@ -67,11 +70,13 @@ import com.android.server.wm.WindowManagerInternal;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;

import java.util.ArrayList;
import java.util.List;
@@ -123,9 +128,10 @@ public class FullScreenMagnificationGestureHandlerTest {
    public static final int STATE_SHORTCUT_TRIGGERED_ZOOMED_TMP = 8;
    public static final int STATE_PANNING = 9;
    public static final int STATE_SCALING_AND_PANNING = 10;
    public static final int STATE_SINGLE_PANNING = 11;

    public static final int FIRST_STATE = STATE_IDLE;
    public static final int LAST_STATE = STATE_SCALING_AND_PANNING;
    public static final int LAST_STATE = STATE_SINGLE_PANNING;

    // Co-prime x and y, to potentially catch x-y-swapped errors
    public static final float DEFAULT_X = 301;
@@ -155,6 +161,10 @@ public class FullScreenMagnificationGestureHandlerTest {

    private float mOriginalMagnificationPersistedScale;

    static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 800, 800);

    static final Region INITIAL_MAGNIFICATION_REGION = new Region(INITIAL_MAGNIFICATION_BOUNDS);

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
@@ -187,6 +197,14 @@ public class FullScreenMagnificationGestureHandlerTest {
                    return true;
                }
        };

        doAnswer((Answer<Void>) invocationOnMock -> {
            Object[] args = invocationOnMock.getArguments();
            Region regionArg = (Region) args[1];
            regionArg.set(new Rect(INITIAL_MAGNIFICATION_BOUNDS));
            return null;
        }).when(mockWindowManager).getMagnificationRegion(anyInt(), any(Region.class));

        mFullScreenMagnificationController.register(DISPLAY_0);
        mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(true);
        mClock = new OffsettableClock.Stopped();
@@ -214,6 +232,7 @@ public class FullScreenMagnificationGestureHandlerTest {
                mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback,
                detectTripleTap, detectShortcutTrigger,
                mWindowMagnificationPromptController, DISPLAY_0);
        h.setSinglePanningEnabled(true);
        mHandler = new TestHandler(h.mDetectingState, mClock) {
            @Override
            protected String messageToString(Message m) {
@@ -239,6 +258,7 @@ public class FullScreenMagnificationGestureHandlerTest {
     * {@link #returnToNormalFrom} (for navigating back to {@link #STATE_IDLE})
     */
    @Test
    @Ignore("b/291925580")
    public void testEachState_isReachableAndRecoverable() {
        forEachState(state -> {
            goFromStateIdleTo(state);
@@ -525,6 +545,75 @@ public class FullScreenMagnificationGestureHandlerTest {
        verify(mWindowMagnificationPromptController).showNotificationIfNeeded();
    }

    @Test
    public void testActionUpNotAtEdge_singlePanningState_detectingState() {
        goFromStateIdleTo(STATE_SINGLE_PANNING);

        send(upEvent());

        check(mMgh.mCurrentState == mMgh.mDetectingState, STATE_IDLE);
        assertTrue(isZoomed());
    }

    @Test
    public void testScroll_SinglePanningDisabled_delegatingState() {
        mMgh.setSinglePanningEnabled(false);

        goFromStateIdleTo(STATE_ACTIVATED);
        allowEventDelegation();
        swipeAndHold();

        assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState);
    }

    @Test
    public void testScroll_zoomedStateAndAtEdge_delegatingState() {
        goFromStateIdleTo(STATE_ACTIVATED);
        mFullScreenMagnificationController.setCenter(
                DISPLAY_0,
                INITIAL_MAGNIFICATION_BOUNDS.left,
                INITIAL_MAGNIFICATION_BOUNDS.top / 2,
                false,
                1);
        final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
        PointF initCoords =
                new PointF(
                        mFullScreenMagnificationController.getCenterX(DISPLAY_0),
                        mFullScreenMagnificationController.getCenterY(DISPLAY_0));
        PointF endCoords = new PointF(initCoords.x, initCoords.y);
        endCoords.offset(swipeMinDistance, 0);
        allowEventDelegation();

        swipeAndHold(initCoords, endCoords);

        assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState);
        assertTrue(isZoomed());
    }

    @Test
    public void testScroll_singlePanningAndAtEdge_delegatingState() {
        goFromStateIdleTo(STATE_SINGLE_PANNING);
        mFullScreenMagnificationController.setCenter(
                DISPLAY_0,
                INITIAL_MAGNIFICATION_BOUNDS.left,
                INITIAL_MAGNIFICATION_BOUNDS.top / 2,
                false,
                1);
        final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
        PointF initCoords =
                new PointF(
                        mFullScreenMagnificationController.getCenterX(DISPLAY_0),
                        mFullScreenMagnificationController.getCenterY(DISPLAY_0));
        PointF endCoords = new PointF(initCoords.x, initCoords.y);
        endCoords.offset(swipeMinDistance, 0);
        allowEventDelegation();

        swipeAndHold(initCoords, endCoords);

        assertTrue(mMgh.mCurrentState == mMgh.mDelegatingState);
        assertTrue(isZoomed());
    }

    @Test
    public void testShortcutTriggered_invokeShowWindowPromptAction() {
        goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
@@ -740,6 +829,10 @@ public class FullScreenMagnificationGestureHandlerTest {
                        state);
                check(mMgh.mPanningScalingState.mScaling, state);
            } break;
            case STATE_SINGLE_PANNING: {
                check(isZoomed(), state);
                check(mMgh.mCurrentState == mMgh.mSinglePanningState, state);
            } break;
            default: throw new IllegalArgumentException("Illegal state: " + state);
        }
    }
@@ -803,6 +896,10 @@ public class FullScreenMagnificationGestureHandlerTest {
                    send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 4));
                    send(pointerEvent(ACTION_MOVE, DEFAULT_X * 2, DEFAULT_Y * 5));
                } break;
                case STATE_SINGLE_PANNING: {
                    goFromStateIdleTo(STATE_ACTIVATED);
                    swipeAndHold();
                } break;
                default:
                    throw new IllegalArgumentException("Illegal state: " + state);
            }
@@ -859,6 +956,10 @@ public class FullScreenMagnificationGestureHandlerTest {
            case STATE_SCALING_AND_PANNING: {
                returnToNormalFrom(STATE_PANNING);
            } break;
            case STATE_SINGLE_PANNING: {
                send(upEvent());
                returnToNormalFrom(STATE_ACTIVATED);
            } break;
            default: throw new IllegalArgumentException("Illegal state: " + state);
        }
    }
@@ -906,6 +1007,11 @@ public class FullScreenMagnificationGestureHandlerTest {
        send(moveEvent(DEFAULT_X * 2, DEFAULT_Y * 2));
    }

    private void swipeAndHold(PointF start, PointF end) {
        send(downEvent(start.x, start.y));
        send(moveEvent(end.x, end.y));
    }

    private void longTap() {
        send(downEvent());
        fastForward(2000);