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