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

Commit aaaab629 authored by Vania Desmonda's avatar Vania Desmonda
Browse files

Disable the PiP dismiss target if input is not touch screen.

This is a product behaviour change as the dismiss target might not be
intuitive for non-touch inputs (see linked bug).

Flag: com.android.window.flags.enable_dragging_pip_across_displays
Fixes: 414604420
Test: atest PipTouchHandlerTest
Change-Id: Ic39275ecae949e704c990c0dfc47887014cf9b07
parent 944aefd0
Loading
Loading
Loading
Loading
+25 −7
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.Size;
import android.view.DisplayCutout;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -98,7 +99,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
    @NonNull private final SizeSpecSource mSizeSpecSource;
    @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
    private final PipUiEventLogger mPipUiEventLogger;
    private final PipDismissTargetHandler mPipDismissTargetHandler;
    private PipDismissTargetHandler mPipDismissTargetHandler;
    private final ShellExecutor mMainExecutor;
    @Nullable private final PipPerfHintController mPipPerfHintController;

@@ -554,6 +555,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
        }

        if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
                && isDismissTargetEnabled(ev)
                && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
            // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
            // to the touch state. Touch state needs a DOWN event in order to later process MOVE
@@ -787,22 +789,29 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
        return mMotionHelper;
    }

    @VisibleForTesting
    public PipResizeGestureHandler getPipResizeGestureHandler() {
    @VisibleForTesting PipResizeGestureHandler getPipResizeGestureHandler() {
        return mPipResizeGestureHandler;
    }

    @VisibleForTesting
    public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
    void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
        mPipResizeGestureHandler = pipResizeGestureHandler;
    }

    @VisibleForTesting PipDismissTargetHandler getPipDismissTargetHandler() {
        return mPipDismissTargetHandler;
    }

    @VisibleForTesting
    public void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
    void setPipDismissTargetHandler(PipDismissTargetHandler pipDismissTargetHandler) {
        mPipDismissTargetHandler = pipDismissTargetHandler;
    }

    @VisibleForTesting void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
        mMotionHelper = pipMotionHelper;
    }

    @VisibleForTesting public void setPipTouchState(PipTouchState pipTouchState) {
    @VisibleForTesting void setPipTouchState(PipTouchState pipTouchState) {
        mTouchState = pipTouchState;
    }

@@ -876,8 +885,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha

            if (touchState.startedDragging()) {
                mSavedSnapFraction = -1f;
                if (isDismissTargetEnabled(touchState.getLatestMotionEvent())) {
                    mPipDismissTargetHandler.showDismissTargetMaybe();
                }
            }

            if (touchState.isDragging()) {
                mPipBoundsState.setHasUserMovedPip(true);
@@ -1083,6 +1094,13 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
        }
    }

    private boolean isDismissTargetEnabled(MotionEvent ev) {
        // Only allow dismiss target to be shown and enabled on touch screen and stylus input
        return !mPipDesktopState.isDraggingPipAcrossDisplaysEnabled()
                || ev.getSource() == InputDevice.SOURCE_TOUCHSCREEN
                || ev.getSource() == InputDevice.SOURCE_STYLUS;
    }

    /**
     * Updates the current movement bounds based on whether the menu is currently visible and
     * resized.
+10 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ public class PipTouchState {
    private final PointF mLastTouch = new PointF();
    private final PointF mLastDelta = new PointF();
    private final PointF mVelocity = new PointF();
    private MotionEvent mLatestMotionEvent;
    private boolean mAllowTouches = true;

    // Set to false to block both PipTouchHandler and PipResizeGestureHandler's input processing
@@ -119,6 +120,7 @@ public class PipTouchState {
                initOrResetVelocityTracker();
                addMovementToVelocityTracker(ev);

                mLatestMotionEvent = ev;
                mActivePointerId = ev.getPointerId(0);
                if (DEBUG) {
                    ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -247,6 +249,13 @@ public class PipTouchState {
        return mLastTouch;
    }

    /**
     * @return the motion event of the latest touch event.
     */
    public MotionEvent getLatestMotionEvent() {
        return mLatestMotionEvent;
    }

    /**
     * @return the movement delta between the last handled touch event and the previous touch
     * position.
@@ -419,6 +428,7 @@ public class PipTouchState {
        pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
        pw.println(innerPrefix + "mLastDelta=" + mLastDelta);
        pw.println(innerPrefix + "mVelocity=" + mVelocity);
        pw.println(innerPrefix + "mLatestMotionEvent=" + mLatestMotionEvent);
        pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting);
        pw.println(innerPrefix + "mIsDragging=" + mIsDragging);
        pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging);
+79 −3
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import android.graphics.Rect
import android.graphics.RectF
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.InputDevice.SOURCE_MOUSE
import android.view.InputDevice.SOURCE_TOUCHSCREEN
import android.view.MotionEvent
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
@@ -49,6 +52,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@@ -85,6 +89,8 @@ class PipTouchHandlerTest : ShellTestCase() {
    private val mockDisplayLayout = mock<DisplayLayout>()
    private val mockTouchPosition = PointF()
    private val mockPipSurfaceTransactionHelper = mock<PipSurfaceTransactionHelper>()
    private val mockMotionEvent = mock<MotionEvent>()
    private val mockPipDismissTargetHandler = mock<PipDismissTargetHandler>()

    private lateinit var pipTouchHandler: PipTouchHandler
    private lateinit var pipTouchGesture: PipTouchGesture
@@ -108,12 +114,16 @@ class PipTouchHandlerTest : ShellTestCase() {
        )
        pipTouchGesture = pipTouchHandler.touchGesture
        pipTouchHandler.setPipTouchState(pipTouchState)
        pipTouchHandler.setPipDismissTargetHandler(mockPipDismissTargetHandler)

        whenever(pipTouchState.downTouchPosition).thenReturn(mockTouchPosition)
        whenever(pipTouchState.velocity).thenReturn(mockTouchPosition)
        whenever(pipTouchState.lastTouchPosition).thenReturn(mockTouchPosition)
        whenever(pipTouchState.lastTouchDisplayId).thenReturn(ORIGIN_DISPLAY_ID)
        whenever(pipTouchState.lastTouchDelta).thenReturn(mockTouchPosition)
        whenever(pipTouchState.isUserInteracting).thenReturn(true)
        whenever(pipTouchState.allowInputEvents).thenReturn(true)
        whenever(pipTouchState.latestMotionEvent).thenReturn(mockMotionEvent)
        whenever(pipTransitionState.pinnedTaskLeash).thenReturn(mockLeash)
        whenever(mockPipBoundsState.movementBounds).thenReturn(PIP_BOUNDS)
        whenever(mockPipBoundsState.motionBoundsState).thenReturn(mockMotionBoundsState)
@@ -133,11 +143,11 @@ class PipTouchHandlerTest : ShellTestCase() {
    @Test
    fun pipTouchGesture_crossDisplayDragFlagEnabled_onMove_showsMirrors() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(true)
        whenever(pipTouchState.isUserInteracting).thenReturn(true)
        whenever(pipTouchState.isDragging).thenReturn(true)
        // Initialize variables that are set on down
        pipTouchGesture.onDown(pipTouchState)

        pipTouchHandler.handleTouchEvent(mockMotionEvent)
        pipTouchGesture.onMove(pipTouchState)

        ExtendedMockito.verify {
@@ -152,14 +162,14 @@ class PipTouchHandlerTest : ShellTestCase() {
            )
        }
        verify(mockPipDisplayTransferHandler).showDragMirrorOnConnectedDisplays(
             eq(GLOBAL_BOUNDS), eq(ORIGIN_DISPLAY_ID))
            eq(GLOBAL_BOUNDS), eq(ORIGIN_DISPLAY_ID)
        )
        verify(mockPipMotionHelper).movePip(eq(PIP_BOUNDS), eq(true), eq(ORIGIN_DISPLAY_ID))
    }

    @Test
    fun pipTouchGesture_crossDisplayDragFlagEnabled_onUpOnADifferentDisplay_schedulesMovePip() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(true)
        whenever(pipTouchState.isUserInteracting).thenReturn(true)
        pipTouchGesture.onDown(pipTouchState)

        pipTouchHandler.mEnableStash = false
@@ -183,6 +193,72 @@ class PipTouchHandlerTest : ShellTestCase() {
        verify(mockPipDisplayTransferHandler).removeMirrors()
    }

    @Test
    fun handleTouchEvent_crossDisplayFlagDisabled_shouldAllowDismissTargetHandler() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(false)
        whenever(mockMotionEvent.source).thenReturn(SOURCE_MOUSE)

        pipTouchHandler.handleTouchEvent(mockMotionEvent)

        verify(mockPipDismissTargetHandler).maybeConsumeMotionEvent(any())
    }

    @Test
    fun handleTouchEvent_crossDisplayFlagEnabled_touchScreenInput_shouldAllowDismissTargetHandler() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(true)
        whenever(mockMotionEvent.source).thenReturn(SOURCE_TOUCHSCREEN)

        pipTouchHandler.handleTouchEvent(mockMotionEvent)

        verify(mockPipDismissTargetHandler).maybeConsumeMotionEvent(any())
    }

    @Test
    fun handleTouchEvent_crossDisplayFlagEnabled_mouseInput_shouldNotAllowDismissTargetHandler() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(true)
        whenever(mockMotionEvent.source).thenReturn(SOURCE_MOUSE)

        pipTouchHandler.handleTouchEvent(mockMotionEvent)

        verify(mockPipDismissTargetHandler, never()).maybeConsumeMotionEvent(any())
    }

    @Test
    fun onMove_crossDisplayFlagDisabled_shouldShowDismissTarget() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(false)
        whenever(pipTouchState.startedDragging()).thenReturn(true)
        whenever(mockMotionEvent.source).thenReturn(SOURCE_MOUSE)

        pipTouchHandler.handleTouchEvent(mockMotionEvent)
        pipTouchGesture.onMove(pipTouchState)

        verify(mockPipDismissTargetHandler).showDismissTargetMaybe()
    }

    @Test
    fun onMove_crossDisplayFlagEnabled_touchScreenInput_shouldShowDismissTarget() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(true)
        whenever(pipTouchState.startedDragging()).thenReturn(true)
        whenever(mockMotionEvent.source).thenReturn(SOURCE_TOUCHSCREEN)

        pipTouchHandler.handleTouchEvent(mockMotionEvent)
        pipTouchGesture.onMove(pipTouchState)

        verify(mockPipDismissTargetHandler).showDismissTargetMaybe()
    }

    @Test
    fun onMove_crossDisplayFlagEnabled_mouseInput_shouldShowDismissTarget() {
        whenever(mockPipDesktopState.isDraggingPipAcrossDisplaysEnabled()).thenReturn(true)
        whenever(pipTouchState.startedDragging()).thenReturn(true)
        whenever(mockMotionEvent.source).thenReturn(SOURCE_MOUSE)

        pipTouchHandler.handleTouchEvent(mockMotionEvent)
        pipTouchGesture.onMove(pipTouchState)

        verify(mockPipDismissTargetHandler, never()).showDismissTargetMaybe()
    }

    private companion object {
        const val ORIGIN_DISPLAY_ID = 0
        const val TARGET_DISPLAY_ID = 1