summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Gustav Sennton <gsennton@google.com> 2025-03-10 11:46:39 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-10 11:46:39 -0700
commitc1e17d0d54b5b260606be2ad159fc5f632a061b1 (patch)
tree89ee887950cbd3a43a3b73ad88fa55c5d8039340
parent68f764be1bd79b2d11e16bc07017a4eaa79e3d8c (diff)
parent82a602a21001f6835f48426355280b25fb310323 (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
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt100
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java69
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);