1 /* <lambda>null2 * Copyright (C) 2023 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.RectEvaluator 21 import android.animation.ValueAnimator 22 import android.graphics.Rect 23 import android.os.IBinder 24 import android.view.Choreographer 25 import android.view.SurfaceControl 26 import android.view.WindowManager.TRANSIT_CHANGE 27 import android.window.TransitionInfo 28 import android.window.TransitionRequestInfo 29 import android.window.WindowContainerTransaction 30 import androidx.core.animation.addListener 31 import com.android.internal.jank.Cuj 32 import com.android.internal.jank.InteractionJankMonitor 33 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE 34 import com.android.wm.shell.transition.Transitions 35 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener 36 import java.util.function.Supplier 37 38 /** Handles the animation of quick resizing of desktop tasks. */ 39 class ToggleResizeDesktopTaskTransitionHandler( 40 private val transitions: Transitions, 41 private val transactionSupplier: Supplier<SurfaceControl.Transaction>, 42 private val interactionJankMonitor: InteractionJankMonitor, 43 ) : Transitions.TransitionHandler { 44 45 private val rectEvaluator = RectEvaluator(Rect()) 46 private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener 47 48 private var boundsAnimator: Animator? = null 49 private var initialBounds: Rect? = null 50 private var callback: (() -> Unit)? = null 51 52 constructor( 53 transitions: Transitions, 54 interactionJankMonitor: InteractionJankMonitor, 55 ) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor) 56 57 /** 58 * Starts a quick resize transition. 59 * 60 * @param wct WindowContainerTransaction that will update core about the task changes applied 61 * @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the 62 * bounds of the actual task). This is provided so that the animation resizing can begin where 63 * the task leash currently is for smoother UX. 64 */ 65 fun startTransition( 66 wct: WindowContainerTransaction, 67 taskLeashBounds: Rect? = null, 68 callback: (() -> Unit)? = null, 69 ) { 70 transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this) 71 initialBounds = taskLeashBounds 72 this.callback = callback 73 } 74 75 fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { 76 onTaskResizeAnimationListener = listener 77 } 78 79 override fun startAnimation( 80 transition: IBinder, 81 info: TransitionInfo, 82 startTransaction: SurfaceControl.Transaction, 83 finishTransaction: SurfaceControl.Transaction, 84 finishCallback: Transitions.TransitionFinishCallback, 85 ): Boolean { 86 val change = findRelevantChange(info) 87 val leash = change.leash 88 val taskId = checkNotNull(change.taskInfo).taskId 89 val startBounds = initialBounds ?: change.startAbsBounds 90 val endBounds = change.endAbsBounds 91 92 val tx = transactionSupplier.get() 93 boundsAnimator?.cancel() 94 boundsAnimator = 95 ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds) 96 .setDuration(RESIZE_DURATION_MS) 97 .apply { 98 addListener( 99 onStart = { 100 startTransaction 101 .setPosition( 102 leash, 103 startBounds.left.toFloat(), 104 startBounds.top.toFloat(), 105 ) 106 .setWindowCrop(leash, startBounds.width(), startBounds.height()) 107 .show(leash) 108 onTaskResizeAnimationListener.onAnimationStart( 109 taskId, 110 startTransaction, 111 startBounds, 112 ) 113 }, 114 onEnd = { 115 finishTransaction 116 .setPosition( 117 leash, 118 endBounds.left.toFloat(), 119 endBounds.top.toFloat(), 120 ) 121 .setWindowCrop(leash, endBounds.width(), endBounds.height()) 122 .show(leash) 123 onTaskResizeAnimationListener.onAnimationEnd(taskId) 124 finishCallback.onTransitionFinished(null) 125 initialBounds = null 126 boundsAnimator = null 127 interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW) 128 interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW) 129 interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE) 130 callback?.invoke() 131 callback = null 132 }, 133 ) 134 addUpdateListener { anim -> 135 val rect = anim.animatedValue as Rect 136 tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) 137 .setWindowCrop(leash, rect.width(), rect.height()) 138 .show(leash) 139 .setFrameTimeline(Choreographer.getInstance().getVsyncId()) 140 onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect) 141 } 142 start() 143 } 144 return true 145 } 146 147 override fun handleRequest( 148 transition: IBinder, 149 request: TransitionRequestInfo, 150 ): WindowContainerTransaction? { 151 return null 152 } 153 154 private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { 155 val matchingChanges = 156 info.changes.filter { c -> 157 !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE 158 } 159 if (matchingChanges.size != 1) { 160 throw IllegalStateException( 161 "Expected 1 relevant change but found: ${matchingChanges.size}" 162 ) 163 } 164 return matchingChanges.first() 165 } 166 167 private fun isWallpaper(change: TransitionInfo.Change): Boolean = 168 (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0 169 170 private fun isValidTaskChange(change: TransitionInfo.Change): Boolean = 171 change.taskInfo != null && change.taskInfo?.taskId != -1 172 173 companion object { 174 private const val RESIZE_DURATION_MS = 300L 175 } 176 } 177