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