• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.desktopmode
18 
19 import android.animation.Animator
20 import android.animation.AnimatorSet
21 import android.animation.ValueAnimator
22 import android.os.Handler
23 import android.os.IBinder
24 import android.view.Choreographer
25 import android.view.SurfaceControl
26 import android.window.TransitionInfo
27 import android.window.TransitionRequestInfo
28 import android.window.WindowContainerTransaction
29 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY
30 import com.android.internal.jank.InteractionJankMonitor
31 import com.android.wm.shell.common.DisplayController
32 import com.android.wm.shell.shared.animation.Interpolators
33 import com.android.wm.shell.transition.Transitions
34 import kotlin.time.Duration.Companion.milliseconds
35 
36 /** Transition handler for moving a window to a different display. */
37 class DesktopModeMoveToDisplayTransitionHandler(
38     private val animationTransaction: SurfaceControl.Transaction,
39     private val interactionJankMonitor: InteractionJankMonitor,
40     private val shellMainHandler: Handler,
41     private val displayController: DisplayController,
42 ) : Transitions.TransitionHandler {
43 
44     override fun handleRequest(
45         transition: IBinder,
46         request: TransitionRequestInfo,
47     ): WindowContainerTransaction? = null
48 
49     override fun startAnimation(
50         transition: IBinder,
51         info: TransitionInfo,
52         startTransaction: SurfaceControl.Transaction,
53         finishTransaction: SurfaceControl.Transaction,
54         finishCallback: Transitions.TransitionFinishCallback,
55     ): Boolean {
56         val changes = info.changes.filter { it.startDisplayId != it.endDisplayId }
57         if (changes.isEmpty()) return false
58         for (change in changes) {
59             val endBounds = change.endAbsBounds
60             // The position should be relative to the parent. For example, in ActivityEmbedding, the
61             // leash surface for the embedded Activity is parented to the container.
62             val endPosition = change.endRelOffset
63             startTransaction
64                 .setPosition(change.leash, endPosition.x.toFloat(), endPosition.y.toFloat())
65                 .setWindowCrop(change.leash, endBounds.width(), endBounds.height())
66         }
67         startTransaction.apply()
68 
69         val animator = AnimatorSet()
70         animator.playTogether(
71             changes.map {
72                 ValueAnimator.ofFloat(0f, 1f).apply {
73                     duration = ANIM_DURATION.inWholeMilliseconds
74                     interpolator = Interpolators.LINEAR
75                     addUpdateListener { animation ->
76                         animationTransaction
77                             .setAlpha(it.leash, animation.animatedValue as Float)
78                             .setFrameTimeline(Choreographer.getInstance().vsyncId)
79                             .apply()
80                     }
81                 }
82             }
83         )
84 
85         animator.addListener(
86             object : Animator.AnimatorListener {
87                 override fun onAnimationStart(animation: Animator) {
88                     val displayContext =
89                         displayController.getDisplayContext(changes[0].endDisplayId)
90                     if (displayContext == null) return
91                     interactionJankMonitor.begin(
92                         changes[0].leash,
93                         displayContext,
94                         shellMainHandler,
95                         CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY,
96                     )
97                 }
98 
99                 override fun onAnimationEnd(animation: Animator) {
100                     finishTransaction.apply()
101                     finishCallback.onTransitionFinished(null)
102                     interactionJankMonitor.end(CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY)
103                 }
104 
105                 override fun onAnimationCancel(animation: Animator) {
106                     finishTransaction.apply()
107                     finishCallback.onTransitionFinished(null)
108                     interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MOVE_WINDOW_TO_DISPLAY)
109                 }
110 
111                 override fun onAnimationRepeat(animation: Animator) = Unit
112             }
113         )
114         animator.start()
115         return true
116     }
117 
118     private companion object {
119         val ANIM_DURATION = 100.milliseconds
120     }
121 }
122