diff options
author | 2025-03-10 11:46:39 -0700 | |
---|---|---|
committer | 2025-03-10 11:46:39 -0700 | |
commit | c1e17d0d54b5b260606be2ad159fc5f632a061b1 (patch) | |
tree | 89ee887950cbd3a43a3b73ad88fa55c5d8039340 | |
parent | 68f764be1bd79b2d11e16bc07017a4eaa79e3d8c (diff) | |
parent | 82a602a21001f6835f48426355280b25fb310323 (diff) |
Merge changes I7b7c0f40,Id0ea7f57 into main
* changes:
[3/N] Report home visibility update on cancelling DragToDesktop transition
[2/N] Interrupt DragToDesktop before bookend request -> move to Home
7 files changed, 326 insertions, 64 deletions
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 1f0774c24143..d0adff2307c6 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 @@ -32,8 +32,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.graphics.Point import android.graphics.PointF import android.graphics.Rect @@ -654,6 +652,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, dragToDesktopValueAnimator: MoveToDesktopAnimator, taskSurface: SurfaceControl, + dragInterruptedCallback: Runnable, ) { logV("startDragToDesktop taskId=%d", taskInfo.taskId) val jankConfigBuilder = @@ -669,6 +668,7 @@ class DesktopTasksController( taskInfo, dragToDesktopValueAnimator, visualIndicator, + dragInterruptedCallback, ) } 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 24b2e4879546..c6f74728fd81 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 @@ -2,6 +2,7 @@ package com.android.wm.shell.desktopmode import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet import android.animation.RectEvaluator import android.animation.ValueAnimator import android.app.ActivityManager.RunningTaskInfo @@ -23,6 +24,7 @@ import android.os.IBinder import android.os.SystemClock import android.os.SystemProperties import android.os.UserHandle +import android.view.Choreographer import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CLOSE @@ -48,6 +50,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKT import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT @@ -122,6 +125,7 @@ sealed class DragToDesktopTransitionHandler( taskInfo: RunningTaskInfo, dragToDesktopAnimator: MoveToDesktopAnimator, visualIndicator: DesktopModeVisualIndicator?, + dragCancelCallback: Runnable, ) { if (inProgress) { logV("Drag to desktop transition already in progress.") @@ -168,6 +172,7 @@ sealed class DragToDesktopTransitionHandler( startTransitionToken = startTransitionToken, otherSplitTask = otherTask, visualIndicator = visualIndicator, + dragCancelCallback = dragCancelCallback, ) } else { TransitionState.FromFullscreen( @@ -175,6 +180,7 @@ sealed class DragToDesktopTransitionHandler( dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, visualIndicator = visualIndicator, + dragCancelCallback = dragCancelCallback, ) } } @@ -203,8 +209,9 @@ sealed class DragToDesktopTransitionHandler( } if (state.startInterrupted) { logV("finishDragToDesktop: start was interrupted, returning") - // We should only have interrupted the start transition after receiving a cancel/end - // request, let that existing request play out and just return here. + // If start was interrupted we've either already requested a cancel/end transition - so + // we should let that request play out, or we're cancelling the drag-to-desktop + // transition altogether, so just return here. return null } state.endTransitionToken = @@ -221,6 +228,7 @@ sealed class DragToDesktopTransitionHandler( */ fun cancelDragToDesktopTransition(cancelState: CancelState) { if (!inProgress) { + logV("cancelDragToDesktop: not in progress, returning") // Don't attempt to cancel a drag to desktop transition since there is no transition in // progress which means that the drag to desktop transition was never successfully // started. @@ -228,14 +236,17 @@ sealed class DragToDesktopTransitionHandler( } val state = requireTransitionState() if (state.startAborted) { + logV("cancelDragToDesktop: start was aborted, clearing state") // Don't attempt to cancel the drag-to-desktop since the start transition didn't // succeed as expected. Just reset the state as if nothing happened. clearState() return } if (state.startInterrupted) { - // We should only have interrupted the start transition after receiving a cancel/end - // request, let that existing request play out and just return here. + logV("cancelDragToDesktop: start was interrupted, returning") + // If start was interrupted we've either already requested a cancel/end transition - so + // we should let that request play out, or we're cancelling the drag-to-desktop + // transition altogether, so just return here. return } state.cancelState = cancelState @@ -706,11 +717,7 @@ sealed class DragToDesktopTransitionHandler( // end-transition, or if the end-transition is running on its own, then just wait until that // finishes instead. If we've merged the cancel-transition we've finished the // start-transition and won't reach this code. - if ( - mergeTarget == state.startTransitionToken && - isCancelOrEndTransitionRequested(state) && - !state.mergedEndTransition - ) { + if (mergeTarget == state.startTransitionToken && !state.mergedEndTransition) { interruptStartTransition(state) } } @@ -722,9 +729,23 @@ sealed class DragToDesktopTransitionHandler( if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) { return } - logV("interruptStartTransition") - state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) - state.dragAnimator.cancelAnimator() + if (isCancelOrEndTransitionRequested(state)) { + logV("interruptStartTransition, bookend requested -> finish start transition") + // Finish the start-drag transition, we will finish the overall transition properly when + // receiving #startAnimation for Cancel/End. + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) + state.dragAnimator.cancelAnimator() + } else { + logV("interruptStartTransition, bookend not requested -> animate to Home") + // Animate to Home, and then finish the start-drag transition. Since there is no other + // (end/cancel) transition requested that will be the end of the overall transition. + state.dragAnimator.cancelAnimator() + state.dragCancelCallback?.run() + createInterruptToHomeAnimator(transactionSupplier.get(), state) { + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) + clearState() + } + } state.activeCancelAnimation?.removeAllListeners() state.activeCancelAnimation?.cancel() state.activeCancelAnimation = null @@ -738,6 +759,46 @@ sealed class DragToDesktopTransitionHandler( .onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG) } + private fun createInterruptToHomeAnimator( + transaction: Transaction, + state: TransitionState, + endCallback: Runnable, + ) { + val homeLeash = state.homeChange?.leash ?: error("Expected home leash to be non-null") + val draggedTaskLeash = + state.draggedTaskChange?.leash ?: error("Expected dragged leash to be non-null") + val homeAnimator = createInterruptAlphaAnimator(transaction, homeLeash, toShow = true) + val draggedTaskAnimator = + createInterruptAlphaAnimator(transaction, draggedTaskLeash, toShow = false) + val animatorSet = AnimatorSet() + animatorSet.playTogether(homeAnimator, draggedTaskAnimator) + animatorSet.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + endCallback.run() + } + } + ) + animatorSet.start() + } + + private fun createInterruptAlphaAnimator( + transaction: Transaction, + leash: SurfaceControl, + toShow: Boolean, + ) = + ValueAnimator.ofFloat(if (toShow) 0f else 1f, if (toShow) 1f else 0f).apply { + transaction.show(leash) + duration = DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS + interpolator = Interpolators.LINEAR + addUpdateListener { animation -> + transaction + .setAlpha(leash, animation.animatedValue as Float) + .setFrameTimeline(Choreographer.getInstance().vsyncId) + .apply() + } + } + protected open fun setupEndDragToDesktop( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, @@ -1060,6 +1121,7 @@ sealed class DragToDesktopTransitionHandler( abstract var endTransitionToken: IBinder? abstract var mergedEndTransition: Boolean abstract var activeCancelAnimation: Animator? + abstract var dragCancelCallback: Runnable? data class FromFullscreen( override val draggedTaskId: Int, @@ -1079,6 +1141,7 @@ sealed class DragToDesktopTransitionHandler( override var endTransitionToken: IBinder? = null, override var mergedEndTransition: Boolean = false, override var activeCancelAnimation: Animator? = null, + override var dragCancelCallback: Runnable? = null, var otherRootChanges: MutableList<Change> = mutableListOf(), ) : TransitionState() @@ -1100,6 +1163,7 @@ sealed class DragToDesktopTransitionHandler( override var endTransitionToken: IBinder? = null, override var mergedEndTransition: Boolean = false, override var activeCancelAnimation: Animator? = null, + override var dragCancelCallback: Runnable? = null, var splitRootChange: Change? = null, var otherSplitTask: Int, ) : TransitionState() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java index 938885cc1684..23dfb41d52c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java @@ -18,6 +18,7 @@ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.view.Display.DEFAULT_DISPLAY; +import static android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP; @@ -50,6 +51,7 @@ public class HomeTransitionObserver implements TransitionObserver, private @NonNull final Context mContext; private @NonNull final ShellExecutor mMainExecutor; + private IBinder mPendingStartDragTransition; private Boolean mPendingHomeVisibilityUpdate; public HomeTransitionObserver(@NonNull Context context, @@ -63,31 +65,42 @@ public class HomeTransitionObserver implements TransitionObserver, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction) { - if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) { - handleTransitionReadyWithBubbleAnything(info); - } else { - handleTransitionReady(info); + Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info); + + if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) { + // Do not apply at the start of desktop drag as that updates launcher UI visibility. + // Store the value and apply with a next transition or when cancelling the + // desktop-drag transition. + storePendingHomeVisibilityUpdate(transition, homeVisibilityUpdate); + return; + } + + if (BubbleAnythingFlagHelper.enableBubbleToFullscreen() + && info.getType() == TRANSIT_CONVERT_TO_BUBBLE + && homeVisibilityUpdate == null) { + // We are converting to bubble and we did not get a change to home visibility in this + // transition. Apply the value from start of drag. + homeVisibilityUpdate = mPendingHomeVisibilityUpdate; + } + + if (homeVisibilityUpdate != null) { + mPendingHomeVisibilityUpdate = null; + mPendingStartDragTransition = null; + notifyHomeVisibilityChanged(homeVisibilityUpdate); } } - private void handleTransitionReady(@NonNull TransitionInfo info) { - for (TransitionInfo.Change change : info.getChanges()) { - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo == null - || info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP - || taskInfo.displayId != DEFAULT_DISPLAY - || taskInfo.taskId == -1 - || !taskInfo.isRunning) { - continue; - } - Boolean homeVisibilityUpdate = getHomeVisibilityUpdate(info, change, taskInfo); - if (homeVisibilityUpdate != null) { - notifyHomeVisibilityChanged(homeVisibilityUpdate); - } + private void storePendingHomeVisibilityUpdate( + IBinder transition, Boolean homeVisibilityUpdate) { + if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen() + && !ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) { + return; } + mPendingHomeVisibilityUpdate = homeVisibilityUpdate; + mPendingStartDragTransition = transition; } - private void handleTransitionReadyWithBubbleAnything(@NonNull TransitionInfo info) { + private Boolean getHomeVisibilityUpdate(TransitionInfo info) { Boolean homeVisibilityUpdate = null; for (TransitionInfo.Change change : info.getChanges()) { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -97,29 +110,12 @@ public class HomeTransitionObserver implements TransitionObserver, || !taskInfo.isRunning) { continue; } - Boolean update = getHomeVisibilityUpdate(info, change, taskInfo); if (update != null) { homeVisibilityUpdate = update; } } - - if (info.getType() == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP) { - // Do not apply at the start of desktop drag as that updates launcher UI visibility. - // Store the value and apply with a next transition if needed. - mPendingHomeVisibilityUpdate = homeVisibilityUpdate; - return; - } - - if (info.getType() == TRANSIT_CONVERT_TO_BUBBLE && homeVisibilityUpdate == null) { - // We are converting to bubble and we did not get a change to home visibility in this - // transition. Apply the value from start of drag. - homeVisibilityUpdate = mPendingHomeVisibilityUpdate; - } - if (homeVisibilityUpdate != null) { - mPendingHomeVisibilityUpdate = null; - notifyHomeVisibilityChanged(homeVisibilityUpdate); - } + return homeVisibilityUpdate; } private Boolean getHomeVisibilityUpdate(TransitionInfo info, @@ -146,7 +142,24 @@ public class HomeTransitionObserver implements TransitionObserver, @Override public void onTransitionFinished(@NonNull IBinder transition, - boolean aborted) {} + boolean aborted) { + if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue()) { + return; + } + // Handle the case where the DragToDesktop START transition is interrupted and we never + // receive a CANCEL/END transition. + if (mPendingStartDragTransition == null + || mPendingStartDragTransition != transition) { + return; + } + mPendingStartDragTransition = null; + if (aborted) return; + + if (mPendingHomeVisibilityUpdate != null) { + notifyHomeVisibilityChanged(mPendingHomeVisibilityUpdate); + mPendingHomeVisibilityUpdate = null; + } + } /** * Sets the home transition listener that receives any transitions resulting in a change of diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index a1d2774ee428..febb8850183d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -979,6 +979,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private boolean mIsCustomHeaderGesture; private boolean mIsResizeGesture; private boolean mIsDragging; + private boolean mDragInterrupted; private boolean mLongClickDisabled; private int mDragPointerId = -1; private MotionEvent mMotionEvent; @@ -1216,7 +1217,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, View v, MotionEvent e) { final int id = v.getId(); if (id == R.id.caption_handle) { - handleCaptionThroughStatusBar(e, decoration); + handleCaptionThroughStatusBar(e, decoration, + /* interruptDragCallback= */ + () -> { + mDragInterrupted = true; + setIsDragging(decoration, /* isDragging= */ false); + }); final boolean wasDragging = mIsDragging; updateDragStatus(decoration, e); final boolean upOrCancel = e.getActionMasked() == ACTION_UP @@ -1333,11 +1339,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + mDragInterrupted = false; setIsDragging(decor, false /* isDragging */); break; } case MotionEvent.ACTION_MOVE: { - setIsDragging(decor, true /* isDragging */); + if (!mDragInterrupted) { + setIsDragging(decor, true /* isDragging */); + } break; } } @@ -1458,7 +1467,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, if (!mInImmersiveMode && (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM || mTransitionDragActive)) { - handleCaptionThroughStatusBar(ev, relevantDecor); + handleCaptionThroughStatusBar(ev, relevantDecor, + /* interruptDragCallback= */ () -> {}); } } handleEventOutsideCaption(ev, relevantDecor); @@ -1498,7 +1508,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, * Turn on desktop mode if handle is dragged below status bar. */ private void handleCaptionThroughStatusBar(MotionEvent ev, - DesktopModeWindowDecoration relevantDecor) { + DesktopModeWindowDecoration relevantDecor, Runnable interruptDragCallback) { if (relevantDecor == null) { if (ev.getActionMasked() == ACTION_UP) { mMoveToDesktopAnimator = null; @@ -1599,7 +1609,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mContext, mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo, - mMoveToDesktopAnimator, relevantDecor.mTaskSurface); + mMoveToDesktopAnimator, relevantDecor.mTaskSurface, + /* dragInterruptedCallback= */ () -> { + // Don't call into DesktopTasksController to cancel the + // transition here - the transition handler already handles + // that (including removing the visual indicator). + mTransitionDragActive = false; + mMoveToDesktopAnimator = null; + relevantDecor.handleDragInterrupted(); + interruptDragCallback.run(); + }); } } if (mMoveToDesktopAnimator != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index bd21f8db122d..5432f6f65afb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -1683,6 +1683,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + /** + * Indicates that an app handle drag has been interrupted, this can happen e.g. if we receive an + * unknown transition during the drag-to-desktop transition. + */ + void handleDragInterrupted() { + if (mResult.mRootView == null) return; + final View handle = mResult.mRootView.findViewById(R.id.caption_handle); + handle.setHovered(false); + handle.setPressed(false); + } + private boolean pointInView(View v, float x, float y) { return v != null && v.getLeft() <= x && v.getRight() >= x && v.getTop() <= y && v.getBottom() >= y; 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 6e7adf368155..dc96694b814d 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 @@ -54,7 +54,9 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.MockitoSession @@ -85,8 +87,17 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @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>() } + @Mock private lateinit var dragCancelCallback: Runnable + @Mock + private lateinit var dragToDesktopStateListener: + DragToDesktopTransitionHandler.DragToDesktopStateListener + + private val transactionSupplier = Supplier { + val transaction = mock<SurfaceControl.Transaction>() + whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction) + whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction) + transaction + } private lateinit var defaultHandler: DragToDesktopTransitionHandler private lateinit var springHandler: SpringDragToDesktopTransitionHandler @@ -104,7 +115,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { Optional.of(bubbleController), transactionSupplier, ) - .apply { setSplitScreenController(splitScreenController) } + .apply { + setSplitScreenController(splitScreenController) + dragToDesktopStateListener = + this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener + } springHandler = SpringDragToDesktopTransitionHandler( context, @@ -115,7 +130,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { Optional.of(bubbleController), transactionSupplier, ) - .apply { setSplitScreenController(splitScreenController) } + .apply { + setSplitScreenController(splitScreenController) + dragToDesktopStateListener = + this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener + } mockitoSession = ExtendedMockito.mockitoSession() .strictness(Strictness.LENIENT) @@ -706,8 +725,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) - fun mergeOtherTransition_cancelAndEndNotYetRequested_doesntInterruptsStartDrag() { + @DisableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_flagDisabled_cancelAndEndNotYetRequested_doesNotInterruptStartDrag() { val finishCallback = mock<Transitions.TransitionFinishCallback>() val task = createTask() defaultHandler.onTaskResizeAnimationListener = mock() @@ -721,6 +740,39 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_cancelAndEndNotYetRequested_interruptsStartDrag() { + val finishCallback = mock<Transitions.TransitionFinishCallback>() + val task = createTask() + defaultHandler.onTaskResizeAnimationListener = mock() + val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback) + + mergeInterruptingTransition(mergeTarget = startTransition) + + verify(dragAnimator).cancelAnimator() + verify(dragCancelCallback).run() + verify(dragToDesktopStateListener).onTransitionInterrupted() + assertThat(defaultHandler.inProgress).isTrue() + // Doesn't finish start transition yet + verify(finishCallback, never()).onTransitionFinished(/* wct= */ anyOrNull()) + } + + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_cancelAndEndNotYetRequested_finishesStartAfterAnimation() { + val finishCallback = mock<Transitions.TransitionFinishCallback>() + val task = createTask() + defaultHandler.onTaskResizeAnimationListener = mock() + val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback) + + mergeInterruptingTransition(mergeTarget = startTransition) + mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS) + + verify(finishCallback).onTransitionFinished(/* wct= */ anyOrNull()) + assertThat(defaultHandler.inProgress).isFalse() + } + + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() { val startDragFinishCallback = mock<Transitions.TransitionFinishCallback>() val task = createTask() @@ -795,6 +847,35 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { verify(dragAnimator, times(2)).startAnimation() } + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun startCancelAnimation_otherTransitionInterruptingAfterCancelRequest_finishImmediately() { + val task1 = createTask() + val startTransition = startDrag(defaultHandler, task1) + val cancelTransition = + cancelDragToDesktopTransition(defaultHandler, CancelState.STANDARD_CANCEL) + mergeInterruptingTransition(mergeTarget = startTransition) + val cancelFinishCallback = mock<Transitions.TransitionFinishCallback>() + val startTransaction = mock<SurfaceControl.Transaction>() + + val didAnimate = + defaultHandler.startAnimation( + transition = requireNotNull(cancelTransition), + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, + draggedTask = task1, + ), + startTransaction = startTransaction, + finishTransaction = mock(), + finishCallback = cancelFinishCallback, + ) + + assertThat(didAnimate).isTrue() + verify(startTransaction).apply() + verify(cancelFinishCallback).onTransitionFinished(/* wct= */ anyOrNull()) + } + private fun mergeInterruptingTransition(mergeTarget: IBinder) { defaultHandler.mergeAnimation( transition = mock<IBinder>(), @@ -942,7 +1023,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) ) .thenReturn(token) - handler.startDragToDesktopTransition(task, dragAnimator, visualIndicator) + handler.startDragToDesktopTransition( + task, + dragAnimator, + visualIndicator, + dragCancelCallback, + ) return token } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index 3099b0f5cf66..a122c3820dcb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX; import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP; import static com.android.wm.shell.transition.Transitions.TRANSIT_CONVERT_TO_BUBBLE; @@ -44,6 +45,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -196,6 +198,73 @@ public class HomeTransitionObserverTest extends ShellTestCase { } @Test + @DisableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX}) + public void startDragToDesktopFinished_flagDisabled_doesNotTriggerCallback() + throws RemoteException { + TransitionInfo info = mock(TransitionInfo.class); + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + when(change.getTaskInfo()).thenReturn(taskInfo); + when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); + when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP); + setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true); + IBinder transition = mock(IBinder.class); + mHomeTransitionObserver.onTransitionReady( + transition, + info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + + mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false); + + verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean()); + } + + @Test + @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX}) + public void startDragToDesktopAborted_doesNotTriggerCallback() throws RemoteException { + TransitionInfo info = mock(TransitionInfo.class); + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + when(change.getTaskInfo()).thenReturn(taskInfo); + when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); + when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP); + setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true); + IBinder transition = mock(IBinder.class); + mHomeTransitionObserver.onTransitionReady( + transition, + info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + + mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ true); + + verify(mListener, never()).onHomeVisibilityChanged(/* isVisible= */ anyBoolean()); + } + + @Test + @EnableFlags({FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX}) + public void startDragToDesktopFinished_triggersCallback() throws RemoteException { + TransitionInfo info = mock(TransitionInfo.class); + TransitionInfo.Change change = mock(TransitionInfo.Change.class); + ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class); + when(change.getTaskInfo()).thenReturn(taskInfo); + when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change))); + when(info.getType()).thenReturn(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP); + setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_OPEN, true); + IBinder transition = mock(IBinder.class); + mHomeTransitionObserver.onTransitionReady( + transition, + info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + + mHomeTransitionObserver.onTransitionFinished(transition, /* aborted= */ false); + + verify(mListener).onHomeVisibilityChanged(/* isVisible= */ true); + } + + @Test @EnableFlags({Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN, Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE}) public void testDragTaskToBubbleOverHome_notifiesHomeIsVisible() throws RemoteException { ActivityManager.RunningTaskInfo homeTask = createTaskInfo(1, ACTIVITY_TYPE_HOME); |