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

Commit 802c2a70 authored by Vania Desmonda's avatar Vania Desmonda
Browse files

Set PiP bounds using local px to prevent wrapping on the origin display.

Demo: https://drive.google.com/file/d/1vW0ya-6G320qjTYdJCha2Jh-FSY5AXLo/view?usp=sharing

Bug: 383403514
Test: atest PipTouchHandler, PipDisplayTransferHandlerTest
Flag: com.android.window.flags.enable_dragging_pip_across_displays
Change-Id: I03dbff6f59563a9f63fa825fac28bf4c7b932fc9
parent 479a5f10
Loading
Loading
Loading
Loading
+2 −26
Original line number Diff line number Diff line
@@ -15,11 +15,8 @@
 */
package com.android.wm.shell.pip2.phone;

import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;

import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
@@ -30,7 +27,6 @@ import android.view.SurfaceControl.Transaction;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
@@ -124,29 +120,9 @@ public class PipDisplayTransferHandler implements
     * position.
     *
     * @param originDisplayId           the display ID where the drag originated from
     * @param currentDisplayId          the current display ID the pointer is on
     * @param originPointerCoordinates  the position of the pointer when it started dragging
     * @param currentPointerCoordinates the position of the pointer it's currently on
     * @param originBounds              the PiP bounds when dragging starts
     * @param globalDpPipBounds         the PiP bounds in display topology-aware global DP
     */
    public void showDragMirrorOnConnectedDisplays(int originDisplayId, int currentDisplayId,
            PointF originPointerCoordinates, PointF currentPointerCoordinates, Rect originBounds) {
        DisplayLayout originDisplayLayout = mDisplayController.getDisplayLayout(originDisplayId);
        DisplayLayout currentDisplayLayout = mDisplayController.getDisplayLayout(currentDisplayId);

        if (originDisplayLayout == null || currentDisplayLayout == null) {
            ProtoLog.w(WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Failed to show drag mirror on connected displays because displayLayout "
                            + "is null", TAG);
            return;
        }

        RectF globalDpPipBounds =
                MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                originDisplayLayout, originPointerCoordinates, originBounds, currentDisplayLayout,
                currentPointerCoordinates.x, currentPointerCoordinates.y
        );

    public void showDragMirrorOnConnectedDisplays(int originDisplayId, RectF globalDpPipBounds) {
        final Transaction transaction = mSurfaceControlTransactionFactory.getTransaction();
        // Iterate through each connected display ID to ensure partial PiP bounds are shown on
        // all corresponding displays while dragging
+44 −19
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
@@ -54,7 +55,9 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -104,6 +107,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
    private final PipDisplayTransferHandler mPipDisplayTransferHandler;
    private final PhonePipMenuController mMenuController;
    private final AccessibilityManager mAccessibilityManager;
    private final DisplayController mDisplayController;

    /**
     * Whether PIP stash is enabled or not. When enabled, if the user flings toward the edge of the
@@ -204,6 +208,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
        mPipBoundsAlgorithm = pipBoundsAlgorithm;
        mPipBoundsState = pipBoundsState;
        mPipDesktopState = pipDesktopState;
        mDisplayController = displayController;

        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
@@ -219,7 +224,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
        mPipDisplayTransferHandler = pipDisplayTransferHandler;
        mPipScheduler.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
        mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
                mMotionHelper, mPipDisplayLayoutState, displayController, mainExecutor);
                mMotionHelper, mPipDisplayLayoutState, mDisplayController, mainExecutor);
        mTouchState = new PipTouchState(ViewConfiguration.get(context),
                () -> {
                    mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
@@ -878,7 +883,34 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha

            if (touchState.isDragging()) {
                mPipBoundsState.setHasUserMovedPip(true);
                final PointF curPos = touchState.getLastTouchPosition();

                if (mPipDesktopState.isDraggingPipAcrossDisplaysEnabled()) {
                    DisplayLayout currentDisplayLayout = mDisplayController.getDisplayLayout(
                            touchState.getLastTouchDisplayId());
                    DisplayLayout displayLayoutOnDown = mDisplayController.getDisplayLayout(
                            mDisplayIdOnDown);

                    if (displayLayoutOnDown == null || currentDisplayLayout == null) {
                        ProtoLog.w(WM_SHELL_PICTURE_IN_PICTURE,
                                "%s: Failed to show drag mirror on connected displays because "
                                        + "displayLayout is null", TAG);
                        return false;
                    }

                    RectF globalDpPipBounds =
                            MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                                    displayLayoutOnDown, mPointerPositionOnDown, mStartBounds,
                                    currentDisplayLayout, curPos.x, curPos.y);

                    // Create mirrors on connected displays to simulate dragging PiP across displays
                    mPipDisplayTransferHandler.showDragMirrorOnConnectedDisplays(mDisplayIdOnDown,
                            globalDpPipBounds);
                    // Set PiP bounds on the origin display in display topology-aware local px
                    mTmpBounds.set(
                            MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
                                    globalDpPipBounds, displayLayoutOnDown));
                } else {
                    // Move the pinned stack freely
                    final PointF lastDelta = touchState.getLastTouchDelta();
                    float lastX = mStartBounds.left + mDelta.x;
@@ -892,17 +924,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha

                    mTmpBounds.set(getPossiblyMotionBounds());
                    mTmpBounds.offsetTo((int) left, (int) top);
                mMotionHelper.movePip(mTmpBounds, true /* isDragging */);

                final PointF curPos = touchState.getLastTouchPosition();

                if (mPipDesktopState.isDraggingPipAcrossDisplaysEnabled()) {
                    // Create mirrors on connected displays to simulate dragging PiP across displays
                    mPipDisplayTransferHandler.showDragMirrorOnConnectedDisplays(mDisplayIdOnDown,
                            touchState.getLastTouchDisplayId(), mPointerPositionOnDown, curPos,
                            mStartBounds);
                }

                mMotionHelper.movePip(mTmpBounds, true /* isDragging */);

                if (mMovementWithinDismiss) {
                    // Track if movement remains near the bottom edge to identify swipe to dismiss
                    mMovementWithinDismiss = curPos.y >= mPipBoundsState.getMovementBounds().bottom;
+18 −12
Original line number Diff line number Diff line
@@ -43,7 +43,6 @@ import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.os.Bundle
import android.testing.TestableResources
import android.util.ArrayMap
@@ -60,6 +59,8 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.times
import com.google.common.truth.Truth.assertThat
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator
import com.android.wm.shell.common.MultiDisplayTestUtil.TestDisplay
import org.junit.Rule
import org.mockito.kotlin.never
@@ -92,6 +93,7 @@ class PipDisplayTransferHandlerTest : ShellTestCase() {
    private lateinit var pipDisplayTransferHandler: PipDisplayTransferHandler

    private val displayIds = intArrayOf(ORIGIN_DISPLAY_ID, TARGET_DISPLAY_ID, SECONDARY_DISPLAY_ID)
    private val displayLayouts = ArrayMap<Int, DisplayLayout>()
    private val mockBounds = Rect()

    @JvmField
@@ -137,6 +139,7 @@ class PipDisplayTransferHandlerTest : ShellTestCase() {
            whenever(mockDisplayController.getDisplay(id)).thenReturn(display)
            val displayLayout =
                TestDisplay.entries.find { it.id == id }?.getSpyDisplayLayout(resources)
            displayLayouts.put(id, displayLayout)
            whenever(mockDisplayController.getDisplayLayout(id)).thenReturn(displayLayout)
        }

@@ -189,10 +192,11 @@ class PipDisplayTransferHandlerTest : ShellTestCase() {

    @Test
    fun showDragMirrorOnConnectedDisplays_hasNotLeftOriginDisplay_shouldNotCreateMirrors() {
        val globalDpBounds = MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
            displayLayouts.get(ORIGIN_DISPLAY_ID)!!, START_DRAG_COORDINATES,
            PIP_BOUNDS, displayLayouts.get(ORIGIN_DISPLAY_ID)!!, 150f, 150f)
        pipDisplayTransferHandler.showDragMirrorOnConnectedDisplays(
            ORIGIN_DISPLAY_ID, ORIGIN_DISPLAY_ID,
            START_DRAG_COORDINATES, PointF(150f, 150f),
            PIP_BOUNDS
            ORIGIN_DISPLAY_ID, globalDpBounds
        )

        verify(mockRootTaskDisplayAreaOrganizer, never()).reparentToDisplayArea(
@@ -214,10 +218,12 @@ class PipDisplayTransferHandlerTest : ShellTestCase() {

    @Test
    fun showDragMirrorOnConnectedDisplays_movedToAnotherDisplay_createsOneMirror() {
        val globalDpBounds = MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
            displayLayouts.get(ORIGIN_DISPLAY_ID)!!, START_DRAG_COORDINATES,
            PIP_BOUNDS, displayLayouts.get(TARGET_DISPLAY_ID)!!,
            TestDisplay.DISPLAY_1.bounds.centerX(), TestDisplay.DISPLAY_1.bounds.centerY())
        pipDisplayTransferHandler.showDragMirrorOnConnectedDisplays(
            ORIGIN_DISPLAY_ID, TARGET_DISPLAY_ID,
            START_DRAG_COORDINATES, TestDisplay.DISPLAY_1.bounds.center(),
            PIP_BOUNDS
            ORIGIN_DISPLAY_ID, globalDpBounds
        )

        verify(mockRootTaskDisplayAreaOrganizer).reparentToDisplayArea(
@@ -240,10 +246,12 @@ class PipDisplayTransferHandlerTest : ShellTestCase() {

    @Test
    fun showDragMirrorOnConnectedDisplays_inBetweenThreeDisplays_createsTwoMirrors() {
        val globalDpBounds = MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
            displayLayouts.get(ORIGIN_DISPLAY_ID)!!, START_DRAG_COORDINATES,
            PIP_BOUNDS, displayLayouts.get(TARGET_DISPLAY_ID)!!,
            1000f, -100f)
        pipDisplayTransferHandler.showDragMirrorOnConnectedDisplays(
            ORIGIN_DISPLAY_ID, TARGET_DISPLAY_ID,
            START_DRAG_COORDINATES, PointF(1000f, -100f),
            PIP_BOUNDS
            ORIGIN_DISPLAY_ID, globalDpBounds
        )

        verify(mockRootTaskDisplayAreaOrganizer).reparentToDisplayArea(
@@ -290,8 +298,6 @@ class PipDisplayTransferHandlerTest : ShellTestCase() {
        assertThat(pipDisplayTransferHandler.mOnDragMirrorPerDisplayId.isEmpty()).isTrue()
    }

    fun RectF.center(): PointF = PointF(this.centerX(), this.centerY())

    companion object {
        const val ORIGIN_DISPLAY_ID = 0
        const val TARGET_DISPLAY_ID = 1
+43 −9
Original line number Diff line number Diff line
@@ -17,13 +17,18 @@ package com.android.wm.shell.pip2.phone

import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.pip.PipBoundsAlgorithm
import com.android.wm.shell.common.pip.PipBoundsState
@@ -36,6 +41,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import java.util.Optional
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -75,12 +81,19 @@ class PipTouchHandlerTest : ShellTestCase() {
    private val pipTouchState = mock<PipTouchState>()
    private val mockMotionBoundsState = mock<PipBoundsState.MotionBoundsState>()
    private val mockLeash = mock<SurfaceControl>()
    private val mockBounds = Rect()
    private val mockDisplayLayout = mock<DisplayLayout>()
    private val mockTouchPosition = PointF()

    private lateinit var pipTouchHandler: PipTouchHandler
    private lateinit var pipTouchGesture: PipTouchGesture

    @JvmField
    @Rule
    val extendedMockitoRule =
        ExtendedMockitoRule.Builder(this)
            .mockStatic(MultiDisplayDragMoveBoundsCalculator::class.java)
            .build()!!

    @Before
    fun setUp() {
        pipTouchHandler = PipTouchHandler(
@@ -100,9 +113,19 @@ class PipTouchHandlerTest : ShellTestCase() {
        whenever(pipTouchState.lastTouchDisplayId).thenReturn(ORIGIN_DISPLAY_ID)
        whenever(pipTouchState.lastTouchDelta).thenReturn(mockTouchPosition)
        whenever(pipTransitionState.pinnedTaskLeash).thenReturn(mockLeash)
        whenever(mockPipBoundsState.movementBounds).thenReturn(mockBounds)
        whenever(mockPipBoundsState.movementBounds).thenReturn(PIP_BOUNDS)
        whenever(mockPipBoundsState.motionBoundsState).thenReturn(mockMotionBoundsState)
        whenever(pipTouchHandler.possiblyMotionBounds).thenReturn(mockBounds)
        whenever(pipTouchHandler.possiblyMotionBounds).thenReturn(PIP_BOUNDS)
        whenever(mockDisplayController.getDisplayLayout(anyInt()))
            .thenReturn(mockDisplayLayout)
        whenever(
            MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(any(), any())
        ).thenReturn(PIP_BOUNDS)
        whenever(
            MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                any(), any(), any(), any(), any(), any()
            )
        ).thenReturn(GLOBAL_BOUNDS)
    }

    @Test
@@ -110,17 +133,26 @@ class PipTouchHandlerTest : ShellTestCase() {
        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)

        pipTouchGesture.onMove(pipTouchState)

        verify(mockPipDisplayTransferHandler).showDragMirrorOnConnectedDisplays(
            anyInt(),
            anyInt(),
            any(),
            any(),
            any()
        ExtendedMockito.verify {
            MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                any(), any(), any(), any(), any(), any()
            )
        }
        ExtendedMockito.verify {
            MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
                eq(GLOBAL_BOUNDS),
                eq(mockDisplayLayout)
            )
        }
        verify(mockPipDisplayTransferHandler).showDragMirrorOnConnectedDisplays(
            eq(ORIGIN_DISPLAY_ID), eq(GLOBAL_BOUNDS))
        verify(mockPipMotionHelper).movePip(eq(PIP_BOUNDS), eq(true))
    }

    @Test
    fun pipTouchGesture_crossDisplayDragFlagEnabled_onUpOnADifferentDisplay_schedulesMovePip() {
@@ -151,5 +183,7 @@ class PipTouchHandlerTest : ShellTestCase() {
    private companion object {
        const val ORIGIN_DISPLAY_ID = 0
        const val TARGET_DISPLAY_ID = 1
        val PIP_BOUNDS = Rect(0, 0, 700, 700)
        val GLOBAL_BOUNDS = RectF(0f, 0f, 400f, 400f)
    }
}
 No newline at end of file