1 /* 2 * 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.util.SparseArray 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.wm.shell.transition.Transitions 32 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE 33 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration 34 import java.util.function.Supplier 35 36 /** Handles the animation of quick resizing of desktop tasks. */ 37 class ToggleResizeDesktopTaskTransitionHandler( 38 private val transitions: Transitions, 39 private val transactionSupplier: Supplier<SurfaceControl.Transaction> 40 ) : Transitions.TransitionHandler { 41 42 private val rectEvaluator = RectEvaluator(Rect()) 43 private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>() 44 45 private var boundsAnimator: Animator? = null 46 47 constructor( 48 transitions: Transitions 49 ) : this(transitions, Supplier { SurfaceControl.Transaction() }) 50 51 /** Starts a quick resize transition. */ 52 fun startTransition( 53 wct: WindowContainerTransaction, 54 taskId: Int, 55 windowDecoration: DesktopModeWindowDecoration 56 ) { 57 // Pause relayout until the transition animation finishes. 58 windowDecoration.incrementRelayoutBlock() 59 transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this) 60 taskToDecorationMap.put(taskId, windowDecoration) 61 } 62 63 override fun startAnimation( 64 transition: IBinder, 65 info: TransitionInfo, 66 startTransaction: SurfaceControl.Transaction, 67 finishTransaction: SurfaceControl.Transaction, 68 finishCallback: Transitions.TransitionFinishCallback 69 ): Boolean { 70 val change = findRelevantChange(info) 71 val leash = change.leash 72 val taskId = checkNotNull(change.taskInfo).taskId 73 val startBounds = change.startAbsBounds 74 val endBounds = change.endAbsBounds 75 val windowDecor = 76 taskToDecorationMap.removeReturnOld(taskId) 77 ?: throw IllegalStateException("Window decoration not found for task $taskId") 78 79 val tx = transactionSupplier.get() 80 boundsAnimator?.cancel() 81 boundsAnimator = 82 ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds) 83 .setDuration(RESIZE_DURATION_MS) 84 .apply { 85 addListener( 86 onStart = { 87 startTransaction 88 .setPosition( 89 leash, 90 startBounds.left.toFloat(), 91 startBounds.top.toFloat() 92 ) 93 .setWindowCrop(leash, startBounds.width(), startBounds.height()) 94 .show(leash) 95 windowDecor.showResizeVeil(startTransaction, startBounds) 96 }, 97 onEnd = { 98 finishTransaction 99 .setPosition( 100 leash, 101 endBounds.left.toFloat(), 102 endBounds.top.toFloat() 103 ) 104 .setWindowCrop(leash, endBounds.width(), endBounds.height()) 105 .show(leash) 106 windowDecor.hideResizeVeil() 107 finishCallback.onTransitionFinished(null) 108 boundsAnimator = null 109 } 110 ) 111 addUpdateListener { anim -> 112 val rect = anim.animatedValue as Rect 113 tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) 114 .setWindowCrop(leash, rect.width(), rect.height()) 115 .show(leash) 116 windowDecor.updateResizeVeil(tx, rect) 117 } 118 start() 119 } 120 return true 121 } 122 123 override fun handleRequest( 124 transition: IBinder, 125 request: TransitionRequestInfo 126 ): WindowContainerTransaction? { 127 return null 128 } 129 130 private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { 131 val matchingChanges = 132 info.changes.filter { c -> 133 !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE 134 } 135 if (matchingChanges.size != 1) { 136 throw IllegalStateException( 137 "Expected 1 relevant change but found: ${matchingChanges.size}" 138 ) 139 } 140 return matchingChanges.first() 141 } 142 143 private fun isWallpaper(change: TransitionInfo.Change): Boolean { 144 return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0 145 } 146 147 private fun isValidTaskChange(change: TransitionInfo.Change): Boolean { 148 return change.taskInfo != null && change.taskInfo?.taskId != -1 149 } 150 151 companion object { 152 private const val RESIZE_DURATION_MS = 300L 153 } 154 } 155