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.wm.shell.desktopmode 18 19 import android.animation.Animator 20 import android.animation.AnimatorSet 21 import android.animation.RectEvaluator 22 import android.animation.ValueAnimator 23 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM 24 import android.content.Context 25 import android.graphics.Rect 26 import android.os.Handler 27 import android.os.IBinder 28 import android.util.TypedValue 29 import android.view.Choreographer 30 import android.view.SurfaceControl.Transaction 31 import android.view.WindowManager 32 import android.window.TransitionInfo 33 import android.window.TransitionRequestInfo 34 import android.window.WindowContainerTransaction 35 import androidx.core.animation.addListener 36 import com.android.app.animation.Interpolators 37 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK 38 import com.android.internal.jank.InteractionJankMonitor 39 import com.android.wm.shell.common.ShellExecutor 40 import com.android.wm.shell.transition.Transitions 41 import java.util.function.Supplier 42 43 /** The [Transitions.TransitionHandler] that handles transitions for closing desktop mode tasks. */ 44 class CloseDesktopTaskTransitionHandler 45 @JvmOverloads 46 constructor( 47 private val context: Context, 48 private val mainExecutor: ShellExecutor, 49 private val animExecutor: ShellExecutor, 50 private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, 51 private val animHandler: Handler, 52 ) : Transitions.TransitionHandler { 53 54 private val runningAnimations = mutableMapOf<IBinder, List<Animator>>() 55 private val interactionJankMonitor = InteractionJankMonitor.getInstance() 56 57 /** Returns null, as it only handles transitions started from Shell. */ handleRequestnull58 override fun handleRequest( 59 transition: IBinder, 60 request: TransitionRequestInfo, 61 ): WindowContainerTransaction? = null 62 63 override fun startAnimation( 64 transition: IBinder, 65 info: TransitionInfo, 66 startTransaction: Transaction, 67 finishTransaction: Transaction, 68 finishCallback: Transitions.TransitionFinishCallback, 69 ): Boolean { 70 if (info.type != WindowManager.TRANSIT_CLOSE) return false 71 val animations = mutableListOf<Animator>() 72 val onAnimFinish: (Animator) -> Unit = { animator -> 73 mainExecutor.execute { 74 // Animation completed 75 animations.remove(animator) 76 if (animations.isEmpty()) { 77 // All animations completed, finish the transition 78 runningAnimations.remove(transition) 79 finishCallback.onTransitionFinished(/* wct= */ null) 80 interactionJankMonitor.end(CUJ_DESKTOP_MODE_CLOSE_TASK) 81 } 82 } 83 } 84 val closingChanges = 85 info.changes.filter { 86 it.mode == WindowManager.TRANSIT_CLOSE && 87 it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM 88 } 89 animations += 90 closingChanges.map { createCloseAnimation(it, finishTransaction, onAnimFinish) } 91 if (animations.isEmpty()) return false 92 runningAnimations[transition] = animations 93 closingChanges.lastOrNull()?.leash?.let { lastChangeLeash -> 94 interactionJankMonitor.begin( 95 lastChangeLeash, 96 context, 97 animHandler, 98 CUJ_DESKTOP_MODE_CLOSE_TASK, 99 ) 100 } 101 animExecutor.execute { animations.forEach(Animator::start) } 102 return true 103 } 104 createCloseAnimationnull105 private fun createCloseAnimation( 106 change: TransitionInfo.Change, 107 finishTransaction: Transaction, 108 onAnimFinish: (Animator) -> Unit, 109 ): Animator { 110 finishTransaction.hide(change.leash) 111 return AnimatorSet().apply { 112 playTogether(createBoundsCloseAnimation(change), createAlphaCloseAnimation(change)) 113 addListener(onEnd = onAnimFinish) 114 } 115 } 116 createBoundsCloseAnimationnull117 private fun createBoundsCloseAnimation(change: TransitionInfo.Change): Animator { 118 val startBounds = change.startAbsBounds 119 val endBounds = 120 Rect(startBounds).apply { 121 // Scale the end bounds of the window down with an anchor in the center 122 inset( 123 (startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(), 124 (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(), 125 ) 126 val offsetY = 127 TypedValue.applyDimension( 128 TypedValue.COMPLEX_UNIT_DIP, 129 CLOSE_ANIM_OFFSET_Y, 130 context.resources.displayMetrics, 131 ) 132 .toInt() 133 offset(/* dx= */ 0, offsetY) 134 } 135 return ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds).apply { 136 duration = CLOSE_ANIM_DURATION_BOUNDS 137 interpolator = Interpolators.STANDARD_ACCELERATE 138 addUpdateListener { animation -> 139 val animBounds = animation.animatedValue as Rect 140 val animScale = 1 - (1 - CLOSE_ANIM_SCALE) * animation.animatedFraction 141 transactionSupplier 142 .get() 143 .setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat()) 144 .setScale(change.leash, animScale, animScale) 145 .setFrameTimeline(Choreographer.getInstance().vsyncId) 146 .apply() 147 } 148 } 149 } 150 createAlphaCloseAnimationnull151 private fun createAlphaCloseAnimation(change: TransitionInfo.Change): Animator = 152 ValueAnimator.ofFloat(1f, 0f).apply { 153 duration = CLOSE_ANIM_DURATION_ALPHA 154 interpolator = Interpolators.LINEAR 155 addUpdateListener { animation -> 156 transactionSupplier 157 .get() 158 .setAlpha(change.leash, animation.animatedValue as Float) 159 .apply() 160 } 161 } 162 163 private companion object { 164 const val CLOSE_ANIM_DURATION_BOUNDS = 200L 165 const val CLOSE_ANIM_DURATION_ALPHA = 100L 166 const val CLOSE_ANIM_SCALE = 0.95f 167 const val CLOSE_ANIM_OFFSET_Y = 36.0f 168 } 169 } 170