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