Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -1400,6 +1400,7 @@ public abstract class WMShellModule { CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler, Optional<DesktopImmersiveController> desktopImmersiveController, DesktopMinimizationTransitionHandler desktopMinimizationTransitionHandler, DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler, ShellInit shellInit, Loading @@ -1419,6 +1420,7 @@ public abstract class WMShellModule { closeDesktopTaskTransitionHandler, desktopImmersiveController.get(), desktopMinimizationTransitionHandler, desktopModeDragAndDropTransitionHandler, interactionJankMonitor, handler, shellInit, Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +18 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.os.Handler import android.os.IBinder import android.view.DragEvent import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_OPEN Loading Loading @@ -54,6 +55,7 @@ class DesktopMixedTransitionHandler( private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, private val desktopMinimizationTransitionHandler: DesktopMinimizationTransitionHandler, private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler, private val interactionJankMonitor: InteractionJankMonitor, @ShellMainThread private val handler: Handler, shellInit: ShellInit, Loading Loading @@ -136,10 +138,12 @@ class DesktopMixedTransitionHandler( taskId: Int?, minimizingTaskId: Int? = null, exitingImmersiveTask: Int? = null, dragEvent: DragEvent? = null, ): IBinder { if ( !DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue && !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue && !DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue ) { return transitions.startTransition(transitionType, wct, /* handler= */ null) } Loading @@ -160,6 +164,7 @@ class DesktopMixedTransitionHandler( launchingTask = taskId, minimizingTask = minimizingTaskId, exitingImmersiveTask = exitingImmersiveTask, dragEvent = dragEvent, ) ) } Loading Loading @@ -298,6 +303,17 @@ class DesktopMixedTransitionHandler( applyMinimizeChangeReparenting(info, minimizeChange, startTransaction) } } if ( DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue && pending.dragEvent != null ) { return desktopModeDragAndDropTransitionHandler.startAnimation( info, pending.dragEvent, startTransaction, finishCallback, ) } if (immersiveExitChange != null) { subAnimationCount = 2 // Animate the immersive exit change separately. Loading Loading @@ -521,6 +537,7 @@ class DesktopMixedTransitionHandler( val launchingTask: Int?, val minimizingTask: Int?, val exitingImmersiveTask: Int?, val dragEvent: DragEvent? = null, ) : PendingMixedTransition() /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropAnimatorHelper.kt +118 −6 Original line number Diff line number Diff line Loading @@ -20,9 +20,19 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.graphics.Rect import android.view.Choreographer import android.view.SurfaceControl.Transaction import android.window.DesktopExperienceFlags import android.window.TransitionInfo.Change import androidx.core.util.Supplier import com.android.wm.shell.animation.FloatProperties import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.POSITION_SPRING_DAMPING_RATIO import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.POSITION_SPRING_STIFFNESS import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.SIZE_SPRING_DAMPING_RATIO import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.SIZE_SPRING_STIFFNESS import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.getAnimationFraction import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.transition.Transitions.TransitionFinishCallback import javax.inject.Inject Loading @@ -30,6 +40,9 @@ import javax.inject.Inject * Helper class for creating and managing animations related to drag and drop operations in Desktop * Mode. This class provides methods to create different types of animations, for example, covers * different animations for tab tearing. * * <p>It utilizes {@link PhysicsAnimator} for physics-based animations and {@link ValueAnimator} for * simpler animations like fade-in.</p> */ class DesktopModeDragAndDropAnimatorHelper @Inject Loading @@ -46,10 +59,14 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction> * information like the view that should be animated (leash) and the start/end values. * @param finishCallback A [TransitionFinishCallback] that will be invoked when the animation * completes. It will inform the caller that the transition is finished. * @return An [Animator] instance configured to perform the change described by the `change` * parameter. * @return An [DesktopModeDragAndDropAnimator] instance configured to perform the change * described by the `change` parameter. */ fun createAnimator(change: Change, finishCallback: TransitionFinishCallback): Animator { fun createAnimator( change: Change, draggedTaskBounds: Rect, finishCallback: TransitionFinishCallback, ): DesktopModeDragAndDropAnimator { val transaction = transactionSupplier.get() val animatorStartedCallback: () -> Unit = { Loading @@ -58,14 +75,72 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction> } val animatorFinishedCallback: () -> Unit = { finishCallback.onTransitionFinished(null) } return createAlphaAnimator(change, animatorStartedCallback, animatorFinishedCallback) return if (DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue) { createSpringAnimator( change, draggedTaskBounds, animatorStartedCallback, animatorFinishedCallback, ) } else { createAlphaAnimator(change, animatorStartedCallback, animatorFinishedCallback) } } private fun createSpringAnimator( change: Change, draggedTaskBounds: Rect, onStart: () -> Unit, onFinish: () -> Unit, ): DesktopModeDragAndDropAnimator { val transaction = transactionSupplier.get() val positionSpringConfig = PhysicsAnimator.SpringConfig(POSITION_SPRING_STIFFNESS, POSITION_SPRING_DAMPING_RATIO) val sizeSpringConfig = PhysicsAnimator.SpringConfig(SIZE_SPRING_STIFFNESS, SIZE_SPRING_DAMPING_RATIO) val endBounds = change.endAbsBounds var hasCalledStart = false return DesktopModeDragAndDropSpringAnimator( PhysicsAnimator.getInstance(Rect(draggedTaskBounds)) // TODO(b/412571881): Add velocity to tab tearing animation .spring(FloatProperties.RECT_X, endBounds.left.toFloat(), positionSpringConfig) .spring(FloatProperties.RECT_Y, endBounds.top.toFloat(), positionSpringConfig) .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig) .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig) .addUpdateListener { animBounds, _ -> if (!hasCalledStart) { onStart.invoke() hasCalledStart = true } val animFraction = getAnimationFraction( startBounds = draggedTaskBounds, endBounds = endBounds, animBounds = animBounds, ) transaction.apply { setAlpha(change.leash, animFraction) setScale(change.leash, animFraction, animFraction) setPosition( change.leash, animBounds.left.toFloat(), animBounds.top.toFloat(), ) setFrameTimeline(Choreographer.getInstance().vsyncId) apply() } } .withEndActions({ onFinish.invoke() }) ) } private fun createAlphaAnimator( change: Change, onStart: () -> Unit, onFinish: () -> Unit, ): Animator { ): DesktopModeDragAndDropAnimator { val transaction = transactionSupplier.get() val alphaAnimator = ValueAnimator() Loading @@ -88,10 +163,47 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction> transaction.apply() } return alphaAnimator return DesktopModeDragAndDropAlphaAnimator(alphaAnimator) } companion object { const val FADE_IN_ANIMATION_DURATION = 300L } } /** * Abstract base class defining the contract for animations related to drag-and-drop operations * within a Desktop Mode feature. * * This abstract class serves as a necessary wrapper to provide compatibility between different * types of animators used in specific drag-and-drop scenarios. Subclasses might use standard * Android `Animator` instances or more specialized animators like `PhysicsAnimator`. */ abstract class DesktopModeDragAndDropAnimator { /** Starts the specific drag-and-drop animation sequence. */ abstract fun start() } /** * A concrete implementation of [DesktopModeDragAndDropAnimator] specifically designed for animating * alpha of the window. * * @param animator The standard Android [Animator] instance that executes the launch animation. */ class DesktopModeDragAndDropAlphaAnimator(val animator: Animator) : DesktopModeDragAndDropAnimator() { override fun start() = animator.start() } /** * A concrete implementation of [DesktopModeDragAndDropAnimator] specifically designed for spring * animations of different properties of the window (position, size etc.) * * @param animator The `PhysicsAnimator<Rect>` instance responsible for the spring animation. */ class DesktopModeDragAndDropSpringAnimator(val animator: PhysicsAnimator<Rect>) : DesktopModeDragAndDropAnimator() { override fun start() = animator.start() } libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt +49 −9 Original line number Diff line number Diff line Loading @@ -15,7 +15,9 @@ */ package com.android.wm.shell.desktopmode import android.graphics.Rect import android.os.IBinder import android.view.DragEvent import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_OPEN import android.window.TransitionInfo Loading @@ -30,39 +32,68 @@ class DesktopModeDragAndDropTransitionHandler( private val animatorHelper: DesktopModeDragAndDropAnimatorHelper, ) : Transitions.TransitionHandler { private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf() private val pendingTransitionTokens: MutableList<Pair<IBinder, DragEvent>> = mutableListOf() /** * Begin a transition when a [android.app.PendingIntent] is dropped without a window to accept * it. */ fun handleDropEvent(wct: WindowContainerTransaction): IBinder { fun handleDropEvent(wct: WindowContainerTransaction, dragEvent: DragEvent): IBinder { val token = transitions.startTransition(TRANSIT_OPEN, wct, this) pendingTransitionTokens.add(token) pendingTransitionTokens.add(Pair(token, dragEvent)) return token } override fun startAnimation( transition: IBinder, /** * Starts the animation for a task transition. * * This function orchestrates the beginning of an animation for a task transition, which * involves hiding the task's leash, setting the initial crop, and initiating the animator. * * @param info The [TransitionInfo] object containing information about the transition. * @param dragEvent The [DragEvent] that triggered the transition, used to extract the initial * dragged task bounds. * @param startTransaction The [WindowContainerTransaction] used to manipulate the window * hierarchy, such as hiding the task's leash. * @param finishCallback The [TransitionFinishCallback] to be called when the animation * finishes. * @return true if transition was handled, false if not (falls-back to default). */ fun startAnimation( info: TransitionInfo, dragEvent: DragEvent, startTransaction: Transaction, finishTransaction: Transaction, finishCallback: TransitionFinishCallback, ): Boolean { if (!pendingTransitionTokens.contains(transition)) return false val draggedTaskBounds = extractBounds(dragEvent) val change = findRelevantChange(info) val leash = change.leash val endBounds = change.endAbsBounds startTransaction .hide(leash) .setWindowCrop(leash, endBounds.width(), endBounds.height()) .apply() val animator = animatorHelper.createAnimator(change, finishCallback) val animator = animatorHelper.createAnimator(change, draggedTaskBounds, finishCallback) animator.start() pendingTransitionTokens.remove(transition) return true } override fun startAnimation( transition: IBinder, info: TransitionInfo, startTransaction: Transaction, finishTransaction: Transaction, finishCallback: TransitionFinishCallback, ): Boolean { val dragEvent = pendingTransitionTokens.firstOrNull { it.first == transition }?.second ?: return false val result = startAnimation(info, dragEvent, startTransaction, finishCallback) pendingTransitionTokens.removeIf { it.first == transition } return result } private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { val matchingChanges = info.changes.filter { change -> Loading @@ -79,6 +110,15 @@ class DesktopModeDragAndDropTransitionHandler( private fun isValidTaskChange(change: TransitionInfo.Change): Boolean = change.taskInfo != null && change.taskInfo?.taskId != -1 private fun extractBounds(dragEvent: DragEvent): Rect { return Rect( /* left= */ dragEvent.x.toInt(), /* top= */ dragEvent.y.toInt(), /* right= */ dragEvent.x.toInt() + dragEvent.dragSurface.width, /* bottom= */ dragEvent.y.toInt() + dragEvent.dragSurface.height, ) } override fun handleRequest( transition: IBinder, request: TransitionRequestInfo, Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +8 −3 Original line number Diff line number Diff line Loading @@ -1461,6 +1461,7 @@ class DesktopTasksController( deskId: Int?, displayId: Int, unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, dragEvent: DragEvent? = null, ): IBinder { logV( "startLaunchTransition type=%s launchingTaskId=%d deskId=%d displayId=%d", Loading Loading @@ -1527,6 +1528,7 @@ class DesktopTasksController( taskId = launchingTaskId, minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, dragEvent = dragEvent, ) } else if (taskIdToMinimize == null) { val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition) Loading Loading @@ -4273,8 +4275,10 @@ class DesktopTasksController( val wct = WindowContainerTransaction() wct.sendPendingIntent(launchIntent, null, opts.toBundle()) if (windowingMode == WINDOWING_MODE_FREEFORM) { if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) { // TODO b/376389593: Use a custom tab tearing transition/animation if ( DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue || DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue ) { val deskId = getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return false startLaunchTransition( TRANSIT_OPEN, Loading @@ -4282,9 +4286,10 @@ class DesktopTasksController( launchingTaskId = null, deskId = deskId, displayId = DEFAULT_DISPLAY, dragEvent = dragEvent, ) } else { desktopModeDragAndDropTransitionHandler.handleDropEvent(wct) desktopModeDragAndDropTransitionHandler.handleDropEvent(wct, dragEvent) } } else { transitions.startTransition(TRANSIT_OPEN, wct, null) Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -1400,6 +1400,7 @@ public abstract class WMShellModule { CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler, Optional<DesktopImmersiveController> desktopImmersiveController, DesktopMinimizationTransitionHandler desktopMinimizationTransitionHandler, DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler, ShellInit shellInit, Loading @@ -1419,6 +1420,7 @@ public abstract class WMShellModule { closeDesktopTaskTransitionHandler, desktopImmersiveController.get(), desktopMinimizationTransitionHandler, desktopModeDragAndDropTransitionHandler, interactionJankMonitor, handler, shellInit, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +18 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.os.Handler import android.os.IBinder import android.view.DragEvent import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_OPEN Loading Loading @@ -54,6 +55,7 @@ class DesktopMixedTransitionHandler( private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, private val desktopMinimizationTransitionHandler: DesktopMinimizationTransitionHandler, private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler, private val interactionJankMonitor: InteractionJankMonitor, @ShellMainThread private val handler: Handler, shellInit: ShellInit, Loading Loading @@ -136,10 +138,12 @@ class DesktopMixedTransitionHandler( taskId: Int?, minimizingTaskId: Int? = null, exitingImmersiveTask: Int? = null, dragEvent: DragEvent? = null, ): IBinder { if ( !DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue && !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue && !DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue ) { return transitions.startTransition(transitionType, wct, /* handler= */ null) } Loading @@ -160,6 +164,7 @@ class DesktopMixedTransitionHandler( launchingTask = taskId, minimizingTask = minimizingTaskId, exitingImmersiveTask = exitingImmersiveTask, dragEvent = dragEvent, ) ) } Loading Loading @@ -298,6 +303,17 @@ class DesktopMixedTransitionHandler( applyMinimizeChangeReparenting(info, minimizeChange, startTransaction) } } if ( DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue && pending.dragEvent != null ) { return desktopModeDragAndDropTransitionHandler.startAnimation( info, pending.dragEvent, startTransaction, finishCallback, ) } if (immersiveExitChange != null) { subAnimationCount = 2 // Animate the immersive exit change separately. Loading Loading @@ -521,6 +537,7 @@ class DesktopMixedTransitionHandler( val launchingTask: Int?, val minimizingTask: Int?, val exitingImmersiveTask: Int?, val dragEvent: DragEvent? = null, ) : PendingMixedTransition() /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropAnimatorHelper.kt +118 −6 Original line number Diff line number Diff line Loading @@ -20,9 +20,19 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.graphics.Rect import android.view.Choreographer import android.view.SurfaceControl.Transaction import android.window.DesktopExperienceFlags import android.window.TransitionInfo.Change import androidx.core.util.Supplier import com.android.wm.shell.animation.FloatProperties import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.POSITION_SPRING_DAMPING_RATIO import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.POSITION_SPRING_STIFFNESS import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.SIZE_SPRING_DAMPING_RATIO import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.SIZE_SPRING_STIFFNESS import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.getAnimationFraction import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.transition.Transitions.TransitionFinishCallback import javax.inject.Inject Loading @@ -30,6 +40,9 @@ import javax.inject.Inject * Helper class for creating and managing animations related to drag and drop operations in Desktop * Mode. This class provides methods to create different types of animations, for example, covers * different animations for tab tearing. * * <p>It utilizes {@link PhysicsAnimator} for physics-based animations and {@link ValueAnimator} for * simpler animations like fade-in.</p> */ class DesktopModeDragAndDropAnimatorHelper @Inject Loading @@ -46,10 +59,14 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction> * information like the view that should be animated (leash) and the start/end values. * @param finishCallback A [TransitionFinishCallback] that will be invoked when the animation * completes. It will inform the caller that the transition is finished. * @return An [Animator] instance configured to perform the change described by the `change` * parameter. * @return An [DesktopModeDragAndDropAnimator] instance configured to perform the change * described by the `change` parameter. */ fun createAnimator(change: Change, finishCallback: TransitionFinishCallback): Animator { fun createAnimator( change: Change, draggedTaskBounds: Rect, finishCallback: TransitionFinishCallback, ): DesktopModeDragAndDropAnimator { val transaction = transactionSupplier.get() val animatorStartedCallback: () -> Unit = { Loading @@ -58,14 +75,72 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction> } val animatorFinishedCallback: () -> Unit = { finishCallback.onTransitionFinished(null) } return createAlphaAnimator(change, animatorStartedCallback, animatorFinishedCallback) return if (DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue) { createSpringAnimator( change, draggedTaskBounds, animatorStartedCallback, animatorFinishedCallback, ) } else { createAlphaAnimator(change, animatorStartedCallback, animatorFinishedCallback) } } private fun createSpringAnimator( change: Change, draggedTaskBounds: Rect, onStart: () -> Unit, onFinish: () -> Unit, ): DesktopModeDragAndDropAnimator { val transaction = transactionSupplier.get() val positionSpringConfig = PhysicsAnimator.SpringConfig(POSITION_SPRING_STIFFNESS, POSITION_SPRING_DAMPING_RATIO) val sizeSpringConfig = PhysicsAnimator.SpringConfig(SIZE_SPRING_STIFFNESS, SIZE_SPRING_DAMPING_RATIO) val endBounds = change.endAbsBounds var hasCalledStart = false return DesktopModeDragAndDropSpringAnimator( PhysicsAnimator.getInstance(Rect(draggedTaskBounds)) // TODO(b/412571881): Add velocity to tab tearing animation .spring(FloatProperties.RECT_X, endBounds.left.toFloat(), positionSpringConfig) .spring(FloatProperties.RECT_Y, endBounds.top.toFloat(), positionSpringConfig) .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig) .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig) .addUpdateListener { animBounds, _ -> if (!hasCalledStart) { onStart.invoke() hasCalledStart = true } val animFraction = getAnimationFraction( startBounds = draggedTaskBounds, endBounds = endBounds, animBounds = animBounds, ) transaction.apply { setAlpha(change.leash, animFraction) setScale(change.leash, animFraction, animFraction) setPosition( change.leash, animBounds.left.toFloat(), animBounds.top.toFloat(), ) setFrameTimeline(Choreographer.getInstance().vsyncId) apply() } } .withEndActions({ onFinish.invoke() }) ) } private fun createAlphaAnimator( change: Change, onStart: () -> Unit, onFinish: () -> Unit, ): Animator { ): DesktopModeDragAndDropAnimator { val transaction = transactionSupplier.get() val alphaAnimator = ValueAnimator() Loading @@ -88,10 +163,47 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction> transaction.apply() } return alphaAnimator return DesktopModeDragAndDropAlphaAnimator(alphaAnimator) } companion object { const val FADE_IN_ANIMATION_DURATION = 300L } } /** * Abstract base class defining the contract for animations related to drag-and-drop operations * within a Desktop Mode feature. * * This abstract class serves as a necessary wrapper to provide compatibility between different * types of animators used in specific drag-and-drop scenarios. Subclasses might use standard * Android `Animator` instances or more specialized animators like `PhysicsAnimator`. */ abstract class DesktopModeDragAndDropAnimator { /** Starts the specific drag-and-drop animation sequence. */ abstract fun start() } /** * A concrete implementation of [DesktopModeDragAndDropAnimator] specifically designed for animating * alpha of the window. * * @param animator The standard Android [Animator] instance that executes the launch animation. */ class DesktopModeDragAndDropAlphaAnimator(val animator: Animator) : DesktopModeDragAndDropAnimator() { override fun start() = animator.start() } /** * A concrete implementation of [DesktopModeDragAndDropAnimator] specifically designed for spring * animations of different properties of the window (position, size etc.) * * @param animator The `PhysicsAnimator<Rect>` instance responsible for the spring animation. */ class DesktopModeDragAndDropSpringAnimator(val animator: PhysicsAnimator<Rect>) : DesktopModeDragAndDropAnimator() { override fun start() = animator.start() }
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt +49 −9 Original line number Diff line number Diff line Loading @@ -15,7 +15,9 @@ */ package com.android.wm.shell.desktopmode import android.graphics.Rect import android.os.IBinder import android.view.DragEvent import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_OPEN import android.window.TransitionInfo Loading @@ -30,39 +32,68 @@ class DesktopModeDragAndDropTransitionHandler( private val animatorHelper: DesktopModeDragAndDropAnimatorHelper, ) : Transitions.TransitionHandler { private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf() private val pendingTransitionTokens: MutableList<Pair<IBinder, DragEvent>> = mutableListOf() /** * Begin a transition when a [android.app.PendingIntent] is dropped without a window to accept * it. */ fun handleDropEvent(wct: WindowContainerTransaction): IBinder { fun handleDropEvent(wct: WindowContainerTransaction, dragEvent: DragEvent): IBinder { val token = transitions.startTransition(TRANSIT_OPEN, wct, this) pendingTransitionTokens.add(token) pendingTransitionTokens.add(Pair(token, dragEvent)) return token } override fun startAnimation( transition: IBinder, /** * Starts the animation for a task transition. * * This function orchestrates the beginning of an animation for a task transition, which * involves hiding the task's leash, setting the initial crop, and initiating the animator. * * @param info The [TransitionInfo] object containing information about the transition. * @param dragEvent The [DragEvent] that triggered the transition, used to extract the initial * dragged task bounds. * @param startTransaction The [WindowContainerTransaction] used to manipulate the window * hierarchy, such as hiding the task's leash. * @param finishCallback The [TransitionFinishCallback] to be called when the animation * finishes. * @return true if transition was handled, false if not (falls-back to default). */ fun startAnimation( info: TransitionInfo, dragEvent: DragEvent, startTransaction: Transaction, finishTransaction: Transaction, finishCallback: TransitionFinishCallback, ): Boolean { if (!pendingTransitionTokens.contains(transition)) return false val draggedTaskBounds = extractBounds(dragEvent) val change = findRelevantChange(info) val leash = change.leash val endBounds = change.endAbsBounds startTransaction .hide(leash) .setWindowCrop(leash, endBounds.width(), endBounds.height()) .apply() val animator = animatorHelper.createAnimator(change, finishCallback) val animator = animatorHelper.createAnimator(change, draggedTaskBounds, finishCallback) animator.start() pendingTransitionTokens.remove(transition) return true } override fun startAnimation( transition: IBinder, info: TransitionInfo, startTransaction: Transaction, finishTransaction: Transaction, finishCallback: TransitionFinishCallback, ): Boolean { val dragEvent = pendingTransitionTokens.firstOrNull { it.first == transition }?.second ?: return false val result = startAnimation(info, dragEvent, startTransaction, finishCallback) pendingTransitionTokens.removeIf { it.first == transition } return result } private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { val matchingChanges = info.changes.filter { change -> Loading @@ -79,6 +110,15 @@ class DesktopModeDragAndDropTransitionHandler( private fun isValidTaskChange(change: TransitionInfo.Change): Boolean = change.taskInfo != null && change.taskInfo?.taskId != -1 private fun extractBounds(dragEvent: DragEvent): Rect { return Rect( /* left= */ dragEvent.x.toInt(), /* top= */ dragEvent.y.toInt(), /* right= */ dragEvent.x.toInt() + dragEvent.dragSurface.width, /* bottom= */ dragEvent.y.toInt() + dragEvent.dragSurface.height, ) } override fun handleRequest( transition: IBinder, request: TransitionRequestInfo, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +8 −3 Original line number Diff line number Diff line Loading @@ -1461,6 +1461,7 @@ class DesktopTasksController( deskId: Int?, displayId: Int, unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, dragEvent: DragEvent? = null, ): IBinder { logV( "startLaunchTransition type=%s launchingTaskId=%d deskId=%d displayId=%d", Loading Loading @@ -1527,6 +1528,7 @@ class DesktopTasksController( taskId = launchingTaskId, minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, dragEvent = dragEvent, ) } else if (taskIdToMinimize == null) { val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition) Loading Loading @@ -4273,8 +4275,10 @@ class DesktopTasksController( val wct = WindowContainerTransaction() wct.sendPendingIntent(launchIntent, null, opts.toBundle()) if (windowingMode == WINDOWING_MODE_FREEFORM) { if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) { // TODO b/376389593: Use a custom tab tearing transition/animation if ( DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue || DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue ) { val deskId = getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return false startLaunchTransition( TRANSIT_OPEN, Loading @@ -4282,9 +4286,10 @@ class DesktopTasksController( launchingTaskId = null, deskId = deskId, displayId = DEFAULT_DISPLAY, dragEvent = dragEvent, ) } else { desktopModeDragAndDropTransitionHandler.handleDropEvent(wct) desktopModeDragAndDropTransitionHandler.handleDropEvent(wct, dragEvent) } } else { transitions.startTransition(TRANSIT_OPEN, wct, null) Loading