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 package com.android.quickstep.util.unfold 17 18 import android.os.Handler 19 import android.os.Trace 20 import android.util.Log 21 import com.android.systemui.unfold.UnfoldTransitionProgressProvider 22 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener 23 24 /** 25 * Transition progress provider wrapper that can preemptively start the transition on demand 26 * without relying on the source provider. When the source provider has started the animation 27 * it switches to it. 28 * 29 * This might be useful when we want to synchronously start the unfold animation and render 30 * the first frame during turning on the screen. For example, this is used in Launcher where 31 * we need to render the first frame of the animation immediately after receiving a configuration 32 * change event so Window Manager will wait for this frame to be rendered before unblocking 33 * the screen. We can't rely on the original transition progress as it starts the animation 34 * after the screen fully turned on (and unblocked), at this moment it is already too late to 35 * start the animation. 36 * 37 * Using this provider we could render the first frame preemptively by sending 'transition started' 38 * and '0' transition progress before the original progress provider sends these events. 39 */ 40 class PreemptiveUnfoldTransitionProgressProvider( 41 private val source: UnfoldTransitionProgressProvider, 42 private val handler: Handler 43 ) : UnfoldTransitionProgressProvider, TransitionProgressListener { 44 45 private val timeoutRunnable = Runnable { 46 if (isRunning) { 47 listeners.forEach { it.onTransitionFinished() } 48 onPreemptiveStartFinished() 49 Log.wtf(TAG, "Timeout occurred when waiting for the source transition to start") 50 } 51 } 52 53 private val listeners = arrayListOf<TransitionProgressListener>() 54 private var isPreemptivelyRunning = false 55 private var isSourceRunning = false 56 57 private val isRunning: Boolean 58 get() = isPreemptivelyRunning || isSourceRunning 59 60 private val sourceListener = 61 object : TransitionProgressListener { 62 override fun onTransitionStarted() { 63 handler.removeCallbacks(timeoutRunnable) 64 65 if (!isRunning) { 66 listeners.forEach { it.onTransitionStarted() } 67 } 68 69 onPreemptiveStartFinished() 70 isSourceRunning = true 71 } 72 73 override fun onTransitionProgress(progress: Float) { 74 if (isRunning) { 75 listeners.forEach { it.onTransitionProgress(progress) } 76 isSourceRunning = true 77 } 78 } 79 80 override fun onTransitionFinishing() { 81 if (isRunning) { 82 listeners.forEach { it.onTransitionFinishing() } 83 isSourceRunning = true 84 } 85 } 86 87 override fun onTransitionFinished() { 88 if (isRunning) { 89 listeners.forEach { it.onTransitionFinished() } 90 } 91 92 isSourceRunning = false 93 onPreemptiveStartFinished() 94 handler.removeCallbacks(timeoutRunnable) 95 } 96 } 97 98 fun init() { 99 source.addCallback(sourceListener) 100 } 101 102 /** 103 * Starts the animation preemptively. 104 * 105 * - If the source provider is already running, this method won't change any behavior 106 * - If the source provider has not started running yet, it will call onTransitionStarted 107 * for all listeners and optionally onTransitionProgress(initialProgress) if supplied. 108 * When the source provider starts the animation it will switch to send progress and finished 109 * events from it. 110 * If the source provider won't start the animation within a timeout, the animation will be 111 * cancelled and onTransitionFinished will be delivered to the current listeners. 112 */ 113 @JvmOverloads 114 fun preemptivelyStartTransition(initialProgress: Float? = null) { 115 if (!isRunning) { 116 Trace.beginAsyncSection("$TAG#startedPreemptively", 0) 117 118 listeners.forEach { it.onTransitionStarted() } 119 initialProgress?.let { progress -> 120 listeners.forEach { it.onTransitionProgress(progress) } 121 } 122 123 handler.removeCallbacks(timeoutRunnable) 124 handler.postDelayed(timeoutRunnable, PREEMPTIVE_UNFOLD_TIMEOUT_MS) 125 } 126 127 isPreemptivelyRunning = true 128 } 129 130 fun cancelPreemptiveStart() { 131 handler.removeCallbacks(timeoutRunnable) 132 if (isRunning) { 133 listeners.forEach { it.onTransitionFinished() } 134 } 135 onPreemptiveStartFinished() 136 } 137 138 private fun onPreemptiveStartFinished() { 139 if (isPreemptivelyRunning) { 140 Trace.endAsyncSection("$TAG#startedPreemptively", 0) 141 isPreemptivelyRunning = false 142 } 143 } 144 145 override fun destroy() { 146 handler.removeCallbacks(timeoutRunnable) 147 source.removeCallback(sourceListener) 148 source.destroy() 149 } 150 151 override fun addCallback(listener: TransitionProgressListener) { 152 listeners += listener 153 } 154 155 override fun removeCallback(listener: TransitionProgressListener) { 156 listeners -= listener 157 } 158 } 159 160 const val TAG = "PreemptiveUnfoldTransitionProgressProvider" 161 const val PREEMPTIVE_UNFOLD_TIMEOUT_MS = 1700L 162