diff options
7 files changed, 189 insertions, 8 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 56de48daf810..70539902f651 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -103,6 +103,10 @@ public class DesktopModeVisualIndicator { return null; } } + + private static boolean isDragToDesktopStartState(DragStartState startState) { + return startState == FROM_FULLSCREEN || startState == FROM_SPLIT; + } } private final VisualIndicatorViewContainer mVisualIndicatorViewContainer; @@ -125,7 +129,12 @@ public class DesktopModeVisualIndicator { @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider, SnapEventHandler snapEventHandler) { SurfaceControl.Builder builder = new SurfaceControl.Builder(); - taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder); + if (!DragStartState.isDragToDesktopStartState(dragStartState) + || !DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue()) { + // In the DragToDesktop transition we attach the indicator to the transition root once + // that is available - for all other cases attach the indicator here. + taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder); + } mVisualIndicatorViewContainer = new VisualIndicatorViewContainer( DesktopModeFlags.ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX.isTrue() ? desktopExecutor : mainExecutor, @@ -159,6 +168,18 @@ public class DesktopModeVisualIndicator { mVisualIndicatorViewContainer.releaseVisualIndicator(); } + /** Reparent the visual indicator to {@code newParent}. */ + void reparentLeash(SurfaceControl.Transaction t, SurfaceControl newParent) { + mVisualIndicatorViewContainer.reparentLeash(t, newParent); + } + + /** Start the fade-in animation. */ + void fadeInIndicator() { + mVisualIndicatorViewContainer.fadeInIndicator( + mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, + mTaskInfo.displayId); + } + /** * Based on the coordinates of the current drag event, determine which indicator type we should * display, including no visible indicator. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 9cacb0c7d2b8..5d3cb86bf584 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -639,6 +639,7 @@ class DesktopTasksController( dragToDesktopTransitionHandler.startDragToDesktopTransition( taskInfo, dragToDesktopValueAnimator, + visualIndicator, ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index d396d8bff2b8..c8f7ea9f885f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -25,6 +25,7 @@ import android.os.SystemProperties import android.os.UserHandle import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CLOSE +import android.window.DesktopModeFlags import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo @@ -118,6 +119,7 @@ sealed class DragToDesktopTransitionHandler( fun startDragToDesktopTransition( taskInfo: RunningTaskInfo, dragToDesktopAnimator: MoveToDesktopAnimator, + visualIndicator: DesktopModeVisualIndicator?, ) { if (inProgress) { logV("Drag to desktop transition already in progress.") @@ -163,12 +165,14 @@ sealed class DragToDesktopTransitionHandler( dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, otherSplitTask = otherTask, + visualIndicator = visualIndicator, ) } else { TransitionState.FromFullscreen( draggedTaskId = taskInfo.taskId, dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, + visualIndicator = visualIndicator, ) } } @@ -457,6 +461,13 @@ sealed class DragToDesktopTransitionHandler( state.surfaceLayers = layers state.startTransitionFinishCb = finishCallback state.startTransitionFinishTransaction = finishTransaction + + val taskChange = state.draggedTaskChange ?: error("Expected non-null task change.") + val taskInfo = taskChange.taskInfo ?: error("Expected non-null task info.") + + if (DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue) { + attachIndicatorToTransitionRoot(state, info, taskInfo, startTransaction) + } startTransaction.apply() if (state.cancelState == CancelState.NO_CANCEL) { @@ -485,8 +496,6 @@ sealed class DragToDesktopTransitionHandler( } else { SPLIT_POSITION_BOTTOM_OR_RIGHT } - val taskInfo = - state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.") val wct = WindowContainerTransaction() restoreWindowOrder(wct) state.startTransitionFinishTransaction?.apply() @@ -511,6 +520,21 @@ sealed class DragToDesktopTransitionHandler( return true } + private fun attachIndicatorToTransitionRoot( + state: TransitionState, + info: TransitionInfo, + taskInfo: RunningTaskInfo, + t: SurfaceControl.Transaction, + ) { + val transitionRoot = info.getRoot(info.findRootIndex(taskInfo.displayId)) + state.visualIndicator?.let { + // Attach the indicator to the transition root so that it's removed at the end of the + // transition regardless of whether we managed to release the indicator. + it.reparentLeash(t, transitionRoot.leash) + it.fadeInIndicator() + } + } + /** * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is @@ -901,6 +925,7 @@ sealed class DragToDesktopTransitionHandler( abstract var surfaceLayers: DragToDesktopLayers? abstract var cancelState: CancelState abstract var startAborted: Boolean + abstract val visualIndicator: DesktopModeVisualIndicator? data class FromFullscreen( override val draggedTaskId: Int, @@ -915,6 +940,7 @@ sealed class DragToDesktopTransitionHandler( override var surfaceLayers: DragToDesktopLayers? = null, override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, + override val visualIndicator: DesktopModeVisualIndicator?, var otherRootChanges: MutableList<Change> = mutableListOf(), ) : TransitionState() @@ -931,6 +957,7 @@ sealed class DragToDesktopTransitionHandler( override var surfaceLayers: DragToDesktopLayers? = null, override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, + override val visualIndicator: DesktopModeVisualIndicator?, var splitRootChange: Change? = null, var otherSplitTask: Int, ) : TransitionState() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt index 919e8164b58e..23562388b3e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt @@ -130,6 +130,12 @@ constructor( } } + /** Reparent the indicator to {@code newParent}. */ + fun reparentLeash(t: SurfaceControl.Transaction, newParent: SurfaceControl) { + val leash = indicatorLeash ?: return + t.reparent(leash, newParent) + } + private fun showIndicator(taskSurface: SurfaceControl, leash: SurfaceControl) { mainExecutor.execute { indicatorLeash = leash @@ -166,7 +172,7 @@ constructor( displayController.getDisplayLayout(taskInfo.displayId) ?: error("Expected to find DisplayLayout for taskId${taskInfo.taskId}.") if (currentType == IndicatorType.NO_INDICATOR) { - fadeInIndicator(layout, newType, taskInfo.displayId, snapEventHandler) + fadeInIndicatorInternal(layout, newType, taskInfo.displayId, snapEventHandler) } else if (newType == IndicatorType.NO_INDICATOR) { fadeOutIndicator( layout, @@ -195,10 +201,22 @@ constructor( } /** + * Fade indicator in as provided type. + * + * Animator fades the indicator in while expanding the bounds outwards. + */ + fun fadeInIndicator(layout: DisplayLayout, type: IndicatorType, displayId: Int) { + if (isReleased) return + desktopExecutor.execute { + fadeInIndicatorInternal(layout, type, displayId, snapEventHandler) + } + } + + /** * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards. */ @VisibleForTesting - fun fadeInIndicator( + fun fadeInIndicatorInternal( layout: DisplayLayout, type: IndicatorType, displayId: Int, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt index dcc9e2415039..fe1dc29181b9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt @@ -20,6 +20,7 @@ import android.animation.AnimatorTestRule import android.app.ActivityManager.RunningTaskInfo import android.graphics.PointF import android.graphics.Rect +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -28,6 +29,7 @@ import android.view.SurfaceControl import androidx.test.filters.SmallTest import com.android.internal.policy.SystemBarUtils import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE +import com.android.window.flags.Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase @@ -45,6 +47,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever /** @@ -345,6 +349,38 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { assertThat(visualIndicator.indicatorBounds).isEqualTo(dropTargetBounds) } + @Test + @DisableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_inTransitionFlagDisabled_isAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) + + verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_fromFreeform_inTransitionFlagEnabled_isAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM) + + verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_fromFullscreen_inTransitionFlagEnabled_notAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) + + verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun createIndicator_fromSplit_inTransitionFlagEnabled_notAttachedToDisplayArea() { + createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) + + verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any()) + } + private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) { visualIndicator = DesktopModeVisualIndicator( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index de55db86d1e7..9588a5c73385 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -11,8 +11,11 @@ import android.graphics.PointF import android.graphics.Rect import android.os.IBinder import android.os.SystemProperties +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_OPEN import android.window.TransitionInfo @@ -23,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE import com.android.internal.jank.InteractionJankMonitor +import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder @@ -78,6 +82,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var homeTaskLeash: SurfaceControl @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories @Mock private lateinit var bubbleController: BubbleController + @Mock private lateinit var visualIndicator: DesktopModeVisualIndicator private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } @@ -740,11 +745,47 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { assertThat(fraction).isWithin(TOLERANCE).of(0f) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun startDrag_indicatorFlagEnabled_attachesIndicatorToTransitionRoot() { + val task = createTask() + val rootLeash = mock<SurfaceControl>() + val startTransaction = mock<SurfaceControl.Transaction>() + startDrag( + defaultHandler, + task, + startTransaction = startTransaction, + transitionRootLeash = rootLeash, + ) + + verify(visualIndicator).reparentLeash(startTransaction, rootLeash) + verify(visualIndicator).fadeInIndicator() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX) + fun startDrag_indicatorFlagDisabled_doesNotAttachIndicatorToTransitionRoot() { + val task = createTask() + val rootLeash = mock<SurfaceControl>() + val startTransaction = mock<SurfaceControl.Transaction>() + startDrag( + defaultHandler, + task, + startTransaction = startTransaction, + transitionRootLeash = rootLeash, + ) + + verify(visualIndicator, never()).reparentLeash(any(), any()) + verify(visualIndicator, never()).fadeInIndicator() + } + private fun startDrag( handler: DragToDesktopTransitionHandler, task: RunningTaskInfo = createTask(), + startTransaction: SurfaceControl.Transaction = mock(), finishTransaction: SurfaceControl.Transaction = mock(), homeChange: TransitionInfo.Change? = createHomeChange(), + transitionRootLeash: SurfaceControl? = null, ): IBinder { whenever(dragAnimator.position).thenReturn(PointF()) // Simulate transition is started and is ready to animate. @@ -756,8 +797,9 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, draggedTask = task, homeChange = homeChange, + rootLeash = transitionRootLeash, ), - startTransaction = mock(), + startTransaction = startTransaction, finishTransaction = finishTransaction, finishCallback = {}, ) @@ -778,7 +820,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) ) .thenReturn(token) - handler.startDragToDesktopTransition(task, dragAnimator) + handler.startDragToDesktopTransition(task, dragAnimator, visualIndicator) return token } @@ -845,6 +887,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { type: Int, draggedTask: RunningTaskInfo, homeChange: TransitionInfo.Change? = createHomeChange(), + rootLeash: SurfaceControl? = null, ) = TransitionInfo(type, /* flags= */ 0).apply { homeChange?.let { addChange(it) } @@ -861,6 +904,9 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { flags = flags or FLAG_IS_WALLPAPER } ) + if (rootLeash != null) { + addRootLeash(DEFAULT_DISPLAY, rootLeash, /* offsetLeft= */ 0, /* offsetTop= */ 0) + } } private fun createHomeChange() = diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt index 4c8cb3823f7e..c7518d5914b4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt @@ -25,6 +25,7 @@ import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display +import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.SurfaceControlViewHost import android.view.View @@ -49,6 +50,8 @@ import org.mockito.Mockito.mock import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions @@ -121,7 +124,7 @@ class VisualIndicatorViewContainerTest : ShellTestCase() { DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, ) desktopExecutor.flushAll() - verify(spyViewContainer).fadeInIndicator(any(), any(), any(), any()) + verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any()) } @Test @@ -265,6 +268,35 @@ class VisualIndicatorViewContainerTest : ShellTestCase() { ) } + @Test + fun fadeInIndicator_callsFadeIn() { + val spyViewContainer = setupSpyViewContainer() + + spyViewContainer.fadeInIndicator( + mock<DisplayLayout>(), + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, + DEFAULT_DISPLAY, + ) + desktopExecutor.flushAll() + + verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any()) + } + + @Test + fun fadeInIndicator_alreadyReleased_doesntCallFadeIn() { + val spyViewContainer = setupSpyViewContainer() + spyViewContainer.releaseVisualIndicator() + + spyViewContainer.fadeInIndicator( + mock<DisplayLayout>(), + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, + DEFAULT_DISPLAY, + ) + desktopExecutor.flushAll() + + verify(spyViewContainer, never()).fadeInIndicatorInternal(any(), any(), any(), any()) + } + private fun setupSpyViewContainer(): VisualIndicatorViewContainer { val viewContainer = VisualIndicatorViewContainer( |