Files
Lawnchair/wmshell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt
T
2026-01-10 20:48:16 +07:00

122 lines
4.8 KiB
Kotlin

/*
* 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.AnimatorSet
import android.animation.ValueAnimator
import android.os.Handler
import android.os.IBinder
import android.view.Choreographer
import android.view.SurfaceControl
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.transition.Transitions
import kotlin.time.Duration.Companion.milliseconds
/** Transition handler for moving a window to a different display. */
class DesktopModeMoveToDisplayTransitionHandler(
private val animationTransaction: SurfaceControl.Transaction,
private val interactionJankMonitor: InteractionJankMonitor,
private val shellMainHandler: Handler,
private val displayController: DisplayController,
) : Transitions.TransitionHandler {
override fun handleRequest(
transition: IBinder,
request: TransitionRequestInfo,
): WindowContainerTransaction? = null
override fun startAnimation(
transition: IBinder,
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
val changes = info.changes.filter { it.startDisplayId != it.endDisplayId }
if (changes.isEmpty()) return false
for (change in changes) {
val endBounds = change.endAbsBounds
// The position should be relative to the parent. For example, in ActivityEmbedding, the
// leash surface for the embedded Activity is parented to the container.
val endPosition = change.endRelOffset
startTransaction
.setPosition(change.leash, endPosition.x.toFloat(), endPosition.y.toFloat())
.setWindowCrop(change.leash, endBounds.width(), endBounds.height())
}
startTransaction.apply()
val animator = AnimatorSet()
animator.playTogether(
changes.map {
ValueAnimator.ofFloat(0f, 1f).apply {
duration = ANIM_DURATION.inWholeMilliseconds
interpolator = Interpolators.LINEAR
addUpdateListener { animation ->
animationTransaction
.setAlpha(it.leash, animation.animatedValue as Float)
.setFrameTimeline(Choreographer.getInstance().vsyncId)
.apply()
}
}
}
)
animator.addListener(
object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
val displayContext =
displayController.getDisplayContext(changes[0].endDisplayId)
if (displayContext == null) return
interactionJankMonitor.begin(
changes[0].leash,
displayContext,
shellMainHandler,
CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY,
)
}
override fun onAnimationEnd(animation: Animator) {
finishTransaction.apply()
finishCallback.onTransitionFinished(null)
interactionJankMonitor.end(CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY)
}
override fun onAnimationCancel(animation: Animator) {
finishTransaction.apply()
finishCallback.onTransitionFinished(null)
interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY)
}
override fun onAnimationRepeat(animation: Animator) = Unit
}
)
animator.start()
return true
}
private companion object {
val ANIM_DURATION = 100.milliseconds
}
}