/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wm.shell.desktopmode
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
/**
* 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.
*
*
It utilizes {@link PhysicsAnimator} for physics-based animations and {@link ValueAnimator} for
* simpler animations like fade-in.
*/
class DesktopModeDragAndDropAnimatorHelper
@Inject
constructor(val context: Context, val transactionSupplier: Supplier) {
/**
* Creates an animator for a given change, incorporating start and finish callbacks.
*
* This function is responsible for creating an animator that handles the visual changes defined
* by the provided [Change] object. It leverages a transaction supplier to manage the transition
* and provides callbacks to be executed when the animation starts and finishes.
*
* @param change The [Change] object describing the desired visual transition. It should contain
* 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 [DesktopModeDragAndDropAnimator] instance configured to perform the change
* described by the `change` parameter.
*/
fun createAnimator(
change: Change,
draggedTaskBounds: Rect,
finishCallback: TransitionFinishCallback,
): DesktopModeDragAndDropAnimator {
val transaction = transactionSupplier.get()
val animatorStartedCallback: () -> Unit = {
transaction.show(change.leash)
transaction.apply()
}
val animatorFinishedCallback: () -> Unit = { finishCallback.onTransitionFinished(null) }
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,
): DesktopModeDragAndDropAnimator {
val transaction = transactionSupplier.get()
val alphaAnimator = ValueAnimator()
alphaAnimator.setFloatValues(0f, 1f)
alphaAnimator.setDuration(FADE_IN_ANIMATION_DURATION)
alphaAnimator.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
onStart.invoke()
}
override fun onAnimationEnd(animation: Animator) {
onFinish.invoke()
}
}
)
alphaAnimator.addUpdateListener { animation: ValueAnimator ->
transaction.setAlpha(change.leash, animation.animatedFraction)
transaction.apply()
}
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` instance responsible for the spring animation.
*/
class DesktopModeDragAndDropSpringAnimator(val animator: PhysicsAnimator) :
DesktopModeDragAndDropAnimator() {
override fun start() = animator.start()
}