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