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.systemui.mediaprojection.appselector.view
18 
19 import android.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.AnimatorSet
22 import android.animation.ValueAnimator
23 import android.annotation.UiThread
24 import android.graphics.Rect
25 import android.os.IBinder
26 import android.os.RemoteException
27 import android.util.Log
28 import android.view.SurfaceControl
29 import android.view.animation.DecelerateInterpolator
30 import android.window.IRemoteTransitionFinishedCallback
31 import android.window.RemoteTransitionStub
32 import android.window.TransitionInfo
33 import android.window.WindowContainerToken
34 import com.android.app.viewcapture.ViewCapture
35 import com.android.internal.policy.TransitionAnimation
36 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.Companion.TAG
37 
38 class RemoteRecentSplitTaskTransitionRunner(
39     private val firstTaskId: Int,
40     private val secondTaskId: Int,
41     private val viewPosition: IntArray,
42     private val screenBounds: Rect,
43     private val handleResult: () -> Unit,
44 ) : RemoteTransitionStub() {
45     override fun startAnimation(
46         transition: IBinder?,
47         info: TransitionInfo?,
48         t: SurfaceControl.Transaction?,
49         finishedCallback: IRemoteTransitionFinishedCallback
50     ) {
51         val launchAnimation = AnimatorSet()
52         var rootCandidate =
53             info!!.changes.firstOrNull {
54                 it.taskInfo?.taskId == firstTaskId || it.taskInfo?.taskId == secondTaskId
55             }
56 
57         // If we could not find a proper root candidate, something went wrong.
58         check(rootCandidate != null) { "Could not find a split root candidate" }
59 
60         // Recurse up the tree until parent is null, then we've found our root.
61         var parentToken: WindowContainerToken? = rootCandidate.parent
62         while (parentToken != null) {
63             rootCandidate = info.getChange(parentToken) ?: break
64             parentToken = rootCandidate.parent
65         }
66 
67         // Make sure nothing weird happened, like getChange() returning null.
68         check(rootCandidate != null) { "Failed to find a root leash" }
69 
70         // Ending position is the full device screen.
71         val startingScale = 0.25f
72 
73         val startX = viewPosition[0]
74         val startY = viewPosition[1]
75         val endX = screenBounds.left
76         val endY = screenBounds.top
77 
78         ViewCapture.MAIN_EXECUTOR.execute {
79             val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
80             with(progressUpdater) {
81                 interpolator = DecelerateInterpolator(1.5f)
82                 setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION.toLong())
83 
84                 addUpdateListener { valueAnimator ->
85                     val progress = valueAnimator.animatedFraction
86 
87                     val x = startX + ((endX - startX) * progress)
88                     val y = startY + ((endY - startY) * progress)
89                     val scale = startingScale + ((1 - startingScale) * progress)
90 
91                     t!!
92                         .setPosition(rootCandidate.leash, x, y)
93                         .setScale(rootCandidate.leash, scale, scale)
94                         .setAlpha(rootCandidate.leash, progress)
95                         .apply()
96                 }
97 
98                 addListener(
99                     object : AnimatorListenerAdapter() {
100                         override fun onAnimationEnd(animation: Animator) {
101                             try {
102                                 onTransitionFinished()
103                                 finishedCallback.onTransitionFinished(null, null)
104                             } catch (e: RemoteException) {
105                                 Log.e(TAG, "Failed to call transition finished callback", e)
106                             }
107                         }
108                     }
109                 )
110             }
111 
112             launchAnimation.play(progressUpdater)
113             launchAnimation.start()
114         }
115     }
116 
117     @Throws(RemoteException::class)
118     override fun onTransitionConsumed(transition: IBinder, aborted: Boolean) {
119         Log.w(TAG, "unexpected consumption of app selector transition: aborted=$aborted")
120     }
121 
122     @UiThread
123     private fun onTransitionFinished() {
124         // After finished transition, then invoke callback to close the app selector, so that
125         // finish animation of app selector does not override the launch animation of the split
126         // tasks
127         handleResult()
128     }
129 }
130