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