• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.systemui.unfold.util
17 
18 import android.os.Handler
19 import android.os.Looper
20 import androidx.annotation.GuardedBy
21 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
22 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
23 import java.util.concurrent.CopyOnWriteArrayList
24 
25 /**
26  * Manages progress listeners that can have smaller lifespan than the unfold animation.
27  *
28  * Allows to limit getting transition updates to only when
29  * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called with
30  * readyToHandleTransition = true
31  *
32  * If the transition has already started by the moment when the clients are ready to play the
33  * transition then it will report transition started callback and current animation progress.
34  */
35 open class ScopedUnfoldTransitionProgressProvider
36 @JvmOverloads
37 constructor(source: UnfoldTransitionProgressProvider? = null) :
38     UnfoldTransitionProgressProvider, TransitionProgressListener {
39 
40     private var progressHandler: Handler? = null
41     private var source: UnfoldTransitionProgressProvider? = null
42 
43     private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
44 
45     private val lock = Object()
46 
47     @GuardedBy("lock") private var isReadyToHandleTransition = false
48     // Accessed only from progress thread
49     private var isTransitionRunning = false
50     // Accessed only from progress thread
51     private var lastTransitionProgress = PROGRESS_UNSET
52 
53     init {
54         setSourceProvider(source)
55     }
56     /**
57      * Sets the source for the unfold transition progress updates. Replaces current provider if it
58      * is already set
59      *
60      * @param provider transition provider that emits transition progress updates
61      */
setSourceProvidernull62     fun setSourceProvider(provider: UnfoldTransitionProgressProvider?) {
63         source?.removeCallback(this)
64 
65         if (provider != null) {
66             source = provider
67             provider.addCallback(this)
68         } else {
69             source = null
70         }
71     }
72 
73     /**
74      * Allows to notify this provide whether the listeners can play the transition or not.
75      *
76      * Call this method with readyToHandleTransition = true when all listeners are ready to consume
77      * the transition progress events.
78      *
79      * Call it with readyToHandleTransition = false when listeners can't process the events.
80      *
81      * Note that this could be called by any thread.
82      */
setReadyToHandleTransitionnull83     fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
84         synchronized(lock) {
85             this.isReadyToHandleTransition = isReadyToHandleTransition
86             val progressHandlerLocal = this.progressHandler
87             if (progressHandlerLocal != null) {
88                 ensureInHandler(progressHandlerLocal) { reportLastProgressIfNeeded() }
89             }
90         }
91     }
92 
93     /** Runs directly if called from the handler thread. Posts otherwise. */
ensureInHandlernull94     private fun ensureInHandler(handler: Handler, f: () -> Unit) {
95         if (handler.looper.isCurrentThread) {
96             f()
97         } else {
98             handler.post(f)
99         }
100     }
101 
reportLastProgressIfNeedednull102     private fun reportLastProgressIfNeeded() {
103         assertInProgressThread()
104         synchronized(lock) {
105             if (!isTransitionRunning) {
106                 return
107             }
108             if (isReadyToHandleTransition) {
109                 listeners.forEach { it.onTransitionStarted() }
110                 if (lastTransitionProgress != PROGRESS_UNSET) {
111                     listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
112                 }
113             } else {
114                 isTransitionRunning = false
115                 listeners.forEach { it.onTransitionFinished() }
116             }
117         }
118     }
119 
addCallbacknull120     override fun addCallback(listener: TransitionProgressListener) {
121         listeners += listener
122     }
123 
removeCallbacknull124     override fun removeCallback(listener: TransitionProgressListener) {
125         listeners -= listener
126     }
127 
destroynull128     override fun destroy() {
129         source?.removeCallback(this)
130         source?.destroy()
131     }
132 
onTransitionStartednull133     override fun onTransitionStarted() {
134         assertInProgressThread()
135         synchronized(lock) {
136             isTransitionRunning = true
137             if (isReadyToHandleTransition) {
138                 listeners.forEach { it.onTransitionStarted() }
139             }
140         }
141     }
142 
onTransitionProgressnull143     override fun onTransitionProgress(progress: Float) {
144         assertInProgressThread()
145         synchronized(lock) {
146             if (isReadyToHandleTransition) {
147                 listeners.forEach { it.onTransitionProgress(progress) }
148             }
149             lastTransitionProgress = progress
150         }
151     }
152 
onTransitionFinishingnull153     override fun onTransitionFinishing() {
154         assertInProgressThread()
155         synchronized(lock) {
156             if (isReadyToHandleTransition) {
157                 listeners.forEach { it.onTransitionFinishing() }
158             }
159         }
160     }
161 
onTransitionFinishednull162     override fun onTransitionFinished() {
163         assertInProgressThread()
164         synchronized(lock) {
165             if (isReadyToHandleTransition) {
166                 listeners.forEach { it.onTransitionFinished() }
167             }
168             isTransitionRunning = false
169             lastTransitionProgress = PROGRESS_UNSET
170         }
171     }
172 
assertInProgressThreadnull173     private fun assertInProgressThread() {
174         val cachedProgressHandler = progressHandler
175         if (cachedProgressHandler == null) {
176             val thisLooper = Looper.myLooper() ?: error("This thread is expected to have a looper.")
177             progressHandler = Handler(thisLooper)
178         } else {
179             check(cachedProgressHandler.looper.isCurrentThread) {
180                 """Receiving unfold transition callback from different threads.
181                     |Current: ${Thread.currentThread()}
182                     |expected: ${cachedProgressHandler.looper.thread}"""
183                     .trimMargin()
184             }
185         }
186     }
187 
188     private companion object {
189         const val PROGRESS_UNSET = -1f
190     }
191 }
192