1 /*
<lambda>null2  * Copyright (C) 2024 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.launcher3.desktop
18 
19 import android.animation.Animator
20 import android.animation.AnimatorSet
21 import android.animation.ValueAnimator
22 import android.content.Context
23 import android.graphics.Rect
24 import android.os.IBinder
25 import android.view.SurfaceControl.Transaction
26 import android.view.WindowManager.TRANSIT_OPEN
27 import android.view.WindowManager.TRANSIT_TO_BACK
28 import android.view.WindowManager.TRANSIT_TO_FRONT
29 import android.window.IRemoteTransitionFinishedCallback
30 import android.window.RemoteTransitionStub
31 import android.window.TransitionInfo
32 import android.window.TransitionInfo.Change
33 import androidx.core.animation.addListener
34 import com.android.app.animation.Interpolators
35 import com.android.internal.policy.ScreenDecorationsUtils
36 import com.android.quickstep.RemoteRunnable
37 import com.android.wm.shell.shared.animation.MinimizeAnimator
38 import com.android.wm.shell.shared.animation.WindowAnimator
39 import java.util.concurrent.Executor
40 
41 /**
42  * [android.window.RemoteTransition] for Desktop app launches.
43  *
44  * This transition supports minimize-changes, i.e. in a launch-transition, if a window is moved back
45  * ([android.view.WindowManager.TRANSIT_TO_BACK]) this transition will apply a minimize animation to
46  * that window.
47  */
48 class DesktopAppLaunchTransition(
49     private val context: Context,
50     private val mainExecutor: Executor,
51     private val launchType: AppLaunchType,
52 ) : RemoteTransitionStub() {
53 
54     enum class AppLaunchType(
55         val boundsAnimationParams: WindowAnimator.BoundsAnimationParams,
56         val alphaDurationMs: Long,
57     ) {
58         LAUNCH(launchBoundsAnimationDef, /* alphaDurationMs= */ 200L),
59         UNMINIMIZE(unminimizeBoundsAnimationDef, /* alphaDurationMs= */ 100L),
60     }
61 
62     override fun startAnimation(
63         token: IBinder,
64         info: TransitionInfo,
65         t: Transaction,
66         transitionFinishedCallback: IRemoteTransitionFinishedCallback,
67     ) {
68         val safeTransitionFinishedCallback = RemoteRunnable {
69             transitionFinishedCallback.onTransitionFinished(/* wct= */ null, /* sct= */ null)
70         }
71         mainExecutor.execute {
72             runAnimators(info, safeTransitionFinishedCallback)
73             t.apply()
74         }
75     }
76 
77     private fun runAnimators(info: TransitionInfo, finishedCallback: RemoteRunnable) {
78         val animators = mutableListOf<Animator>()
79         val animatorFinishedCallback: (Animator) -> Unit = { animator ->
80             animators -= animator
81             if (animators.isEmpty()) finishedCallback.run()
82         }
83         animators += createAnimators(info, animatorFinishedCallback)
84         animators.forEach { it.start() }
85     }
86 
87     private fun createAnimators(
88         info: TransitionInfo,
89         finishCallback: (Animator) -> Unit,
90     ): List<Animator> {
91         val transaction = Transaction()
92         val launchAnimator =
93             createLaunchAnimator(getLaunchChange(info), transaction, finishCallback)
94         val minimizeChange = getMinimizeChange(info) ?: return listOf(launchAnimator)
95         val minimizeAnimator =
96             MinimizeAnimator.create(
97                 context.resources.displayMetrics,
98                 minimizeChange,
99                 transaction,
100                 finishCallback,
101             )
102         return listOf(launchAnimator, minimizeAnimator)
103     }
104 
105     private fun getLaunchChange(info: TransitionInfo): Change =
106         requireNotNull(info.changes.firstOrNull { change -> change.mode in LAUNCH_CHANGE_MODES }) {
107             "expected an app launch Change"
108         }
109 
110     private fun getMinimizeChange(info: TransitionInfo): Change? =
111         info.changes.firstOrNull { change -> change.mode == TRANSIT_TO_BACK }
112 
113     private fun createLaunchAnimator(
114         change: Change,
115         transaction: Transaction,
116         onAnimFinish: (Animator) -> Unit,
117     ): Animator {
118         val boundsAnimator =
119             WindowAnimator.createBoundsAnimator(
120                 context.resources.displayMetrics,
121                 launchType.boundsAnimationParams,
122                 change,
123                 transaction,
124             )
125         val alphaAnimator =
126             ValueAnimator.ofFloat(0f, 1f).apply {
127                 duration = launchType.alphaDurationMs
128                 interpolator = Interpolators.LINEAR
129                 addUpdateListener { animation ->
130                     transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
131                 }
132             }
133         val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) }
134         transaction.setCrop(change.leash, clipRect)
135         transaction.setCornerRadius(
136             change.leash,
137             ScreenDecorationsUtils.getWindowCornerRadius(context),
138         )
139         return AnimatorSet().apply {
140             playTogether(boundsAnimator, alphaAnimator)
141             addListener(onEnd = { animation -> onAnimFinish(animation) })
142         }
143     }
144 
145     companion object {
146         /** Change modes that represent a task becoming visible / launching in Desktop mode. */
147         val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
148 
149         private val launchBoundsAnimationDef =
150             WindowAnimator.BoundsAnimationParams(
151                 durationMs = 600,
152                 startOffsetYDp = 36f,
153                 startScale = 0.95f,
154                 interpolator = Interpolators.STANDARD_DECELERATE,
155             )
156 
157         private val unminimizeBoundsAnimationDef =
158             WindowAnimator.BoundsAnimationParams(
159                 durationMs = 300,
160                 startOffsetYDp = 12f,
161                 startScale = 0.97f,
162                 interpolator = Interpolators.STANDARD_DECELERATE,
163             )
164     }
165 }
166