1 /* <lambda>null2 * Copyright (C) 2019 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.permissioncontroller.permission.data 18 19 import android.util.Log 20 import androidx.annotation.MainThread 21 import androidx.lifecycle.Lifecycle 22 import androidx.lifecycle.Lifecycle.State 23 import androidx.lifecycle.Lifecycle.State.STARTED 24 import androidx.lifecycle.LifecycleObserver 25 import androidx.lifecycle.LifecycleOwner 26 import androidx.lifecycle.LiveData 27 import androidx.lifecycle.MediatorLiveData 28 import androidx.lifecycle.Observer 29 import com.android.permissioncontroller.permission.utils.KotlinUtils 30 import com.android.permissioncontroller.permission.utils.ensureMainThread 31 import com.android.permissioncontroller.permission.utils.getInitializedValue 32 import com.android.permissioncontroller.permission.utils.shortStackTrace 33 import kotlinx.coroutines.Dispatchers.Main 34 import kotlinx.coroutines.GlobalScope 35 import kotlinx.coroutines.launch 36 37 /** 38 * A MediatorLiveData which tracks how long it has been inactive, compares new values before setting 39 * its value (avoiding unnecessary updates), and can calculate the set difference between a list 40 * and a map (used when determining whether or not to add a LiveData as a source). 41 */ 42 abstract class SmartUpdateMediatorLiveData<T> : MediatorLiveData<T>(), 43 DataRepository.InactiveTimekeeper { 44 45 companion object { 46 const val DEBUG_UPDATES = false 47 val LOG_TAG = SmartUpdateMediatorLiveData::class.java.simpleName 48 } 49 50 /** 51 * Boolean, whether or not the value of this uiDataLiveData has been explicitly set yet. 52 * Differentiates between "null value because liveData is new" and "null value because 53 * liveData is invalid" 54 */ 55 var isInitialized = false 56 private set 57 58 /** 59 * Boolean, whether or not this liveData has a stale value or not. Every time the liveData goes 60 * inactive, its data becomes stale, until it goes active again, and is explicitly set. 61 */ 62 var isStale = true 63 private set 64 65 private val staleObservers = mutableListOf<Pair<LifecycleOwner, Observer<in T>>>() 66 67 private val sources = mutableListOf<SmartUpdateMediatorLiveData<*>>() 68 69 private val children = 70 mutableListOf<Triple<SmartUpdateMediatorLiveData<*>, Observer<in T>, Boolean>>() 71 72 private val stacktraceExceptionMessage = "Caller of coroutine" 73 74 @MainThread 75 override fun setValue(newValue: T?) { 76 ensureMainThread() 77 78 if (!isInitialized) { 79 isInitialized = true 80 isStale = false 81 // If we have received an invalid value, and this is the first time we are set, 82 // notify observers. 83 if (newValue == null) { 84 super.setValue(newValue) 85 return 86 } 87 } 88 89 if (valueNotEqual(super.getValue(), newValue)) { 90 isStale = false 91 super.setValue(newValue) 92 } else if (isStale) { 93 isStale = false 94 // We are no longer stale- notify active stale observers we are up-to-date 95 val liveObservers = staleObservers.filter { it.first.lifecycle.currentState >= STARTED } 96 for ((_, observer) in liveObservers) { 97 observer.onChanged(newValue) 98 } 99 100 for ((liveData, observer, shouldUpdate) in children.toList()) { 101 if (liveData.hasActiveObservers() && shouldUpdate) { 102 observer.onChanged(newValue) 103 } 104 } 105 } 106 } 107 108 /** 109 * Update the value of this LiveData. 110 * 111 * This usually results in an IPC when active and no action otherwise. 112 */ 113 @MainThread 114 fun updateIfActive() { 115 if (DEBUG_UPDATES) { 116 Log.i(LOG_TAG, "updateIfActive ${javaClass.simpleName} ${shortStackTrace()}") 117 } 118 onUpdate() 119 } 120 121 @MainThread 122 protected abstract fun onUpdate() 123 124 override var timeWentInactive: Long? = null 125 126 /** 127 * Some LiveDatas have types, like Drawables which do not have a non-default equals method. 128 * Those classes can override this method to change when the value is set upon calling setValue. 129 * 130 * @param valOne The first T to be compared 131 * @param valTwo The second T to be compared 132 * 133 * @return True if the two values are different, false otherwise 134 */ 135 protected open fun valueNotEqual(valOne: T?, valTwo: T?): Boolean { 136 return valOne != valTwo 137 } 138 139 @MainThread 140 fun observeStale(owner: LifecycleOwner, observer: Observer<in T>) { 141 val oldStaleObserver = hasStaleObserver() 142 staleObservers.add(owner to observer) 143 if (owner == ForeverActiveLifecycle) { 144 observeForever(observer) 145 } else { 146 observe(owner, observer) 147 } 148 updateSourceStaleObservers(oldStaleObserver, true) 149 } 150 151 override fun <S : Any?> addSource(source: LiveData<S>, onChanged: Observer<in S>) { 152 addSourceWithError(source, onChanged) 153 } 154 155 private fun <S : Any?> addSourceWithError( 156 source: LiveData<S>, 157 onChanged: Observer<in S>, 158 e: IllegalStateException? = null 159 ) { 160 // Get the stacktrace of the call to addSource, so it isn't lost in any errors 161 val exception = e ?: IllegalStateException(stacktraceExceptionMessage) 162 163 GlobalScope.launch(Main.immediate) { 164 if (source is SmartUpdateMediatorLiveData) { 165 if (source in sources) { 166 return@launch 167 } 168 source.addChild(this@SmartUpdateMediatorLiveData, onChanged, 169 staleObservers.isNotEmpty() || children.any { it.third }) 170 sources.add(source) 171 } 172 try { 173 super.addSource(source, onChanged) 174 } catch (other: IllegalStateException) { 175 throw other.apply { initCause(exception) } 176 } 177 } 178 } 179 180 override fun <S : Any?> removeSource(toRemote: LiveData<S>) { 181 GlobalScope.launch(Main.immediate) { 182 if (toRemote is SmartUpdateMediatorLiveData) { 183 toRemote.removeChild(this@SmartUpdateMediatorLiveData) 184 sources.remove(toRemote) 185 } 186 super.removeSource(toRemote) 187 } 188 } 189 190 /** 191 * Gets the difference between a list and a map of livedatas, and then will add as a source all 192 * livedatas which are in the list, but not the map, and will remove all livedatas which are in 193 * the map, but not the list 194 * 195 * @param desired The list of liveDatas we want in our map, represented by a key 196 * @param have The map of livedatas we currently have as sources 197 * @param getLiveDataFun A function to turn a key into a liveData 198 * @param onUpdateFun An optional function which will update differently based on different 199 * LiveDatas. If blank, will simply call update. 200 */ 201 fun <K, V : LiveData<*>> setSourcesToDifference( 202 desired: Collection<K>, 203 have: MutableMap<K, V>, 204 getLiveDataFun: (K) -> V, 205 onUpdateFun: ((K) -> Unit)? = null 206 ) { 207 // Ensure the map is correct when method returns 208 val (toAdd, toRemove) = KotlinUtils.getMapAndListDifferences(desired, have) 209 for (key in toAdd) { 210 have[key] = getLiveDataFun(key) 211 } 212 213 val removed = toRemove.map { have.remove(it) }.toMutableList() 214 215 val stackTraceException = java.lang.IllegalStateException(stacktraceExceptionMessage) 216 217 GlobalScope.launch(Main.immediate) { 218 // If any state got out of sorts before this coroutine ran, correct it 219 for (key in toRemove) { 220 removed.add(have.remove(key) ?: continue) 221 } 222 223 for (liveData in removed) { 224 removeSource(liveData ?: continue) 225 } 226 227 for (key in toAdd) { 228 val liveData = getLiveDataFun(key) 229 // Should be a no op, but there is a slight possibility it isn't 230 have[key] = liveData 231 val observer = Observer<Any> { 232 if (onUpdateFun != null) { 233 onUpdateFun(key) 234 } else { 235 updateIfActive() 236 } 237 } 238 addSourceWithError(liveData, observer, stackTraceException) 239 } 240 } 241 } 242 243 @MainThread 244 private fun <S : Any?> removeChild(liveData: LiveData<S>) { 245 children.removeIf { it.first == liveData } 246 } 247 248 @MainThread 249 private fun <S : Any?> addChild( 250 liveData: SmartUpdateMediatorLiveData<S>, 251 onChanged: Observer<in T>, 252 sendStaleUpdates: Boolean 253 ) { 254 children.add(Triple(liveData, onChanged, sendStaleUpdates)) 255 } 256 257 @MainThread 258 private fun <S : Any?> updateShouldSendStaleUpdates( 259 liveData: SmartUpdateMediatorLiveData<S>, 260 sendStaleUpdates: Boolean 261 ) { 262 for ((idx, childTriple) in children.withIndex()) { 263 if (childTriple.first == liveData) { 264 children[idx] = Triple(liveData, childTriple.second, sendStaleUpdates) 265 } 266 } 267 } 268 269 @MainThread 270 override fun removeObserver(observer: Observer<in T>) { 271 val oldStaleObserver = hasStaleObserver() 272 staleObservers.removeIf { it.second == observer } 273 super.removeObserver(observer) 274 updateSourceStaleObservers(oldStaleObserver, hasStaleObserver()) 275 } 276 277 @MainThread 278 override fun removeObservers(owner: LifecycleOwner) { 279 val oldStaleObserver = hasStaleObserver() 280 staleObservers.removeIf { it.first == owner } 281 super.removeObservers(owner) 282 updateSourceStaleObservers(oldStaleObserver, hasStaleObserver()) 283 } 284 285 @MainThread 286 override fun observeForever(observer: Observer<in T>) { 287 super.observeForever(observer) 288 } 289 290 @MainThread 291 private fun updateSourceStaleObservers(hadStaleObserver: Boolean, hasStaleObserver: Boolean) { 292 if (hadStaleObserver == hasStaleObserver) { 293 return 294 } 295 for (liveData in sources) { 296 liveData.updateShouldSendStaleUpdates(this, hasStaleObserver) 297 } 298 299 // if all sources are not stale, and we just requested stale updates, and we are stale, 300 // update our value 301 if (sources.all { !it.isStale } && hasStaleObserver && isStale) { 302 updateIfActive() 303 } 304 } 305 306 private fun hasStaleObserver(): Boolean { 307 return staleObservers.isNotEmpty() || children.any { it.third } 308 } 309 310 override fun onActive() { 311 timeWentInactive = null 312 super.onActive() 313 } 314 315 override fun onInactive() { 316 timeWentInactive = System.nanoTime() 317 isStale = true 318 super.onInactive() 319 } 320 321 /** 322 * Get the [initialized][isInitialized] value, suspending until one is available 323 * 324 * @param staleOk whether [isStale] value is ok to return 325 * @param forceUpdate whether to call [updateIfActive] (usually triggers an IPC) 326 */ 327 suspend fun getInitializedValue(staleOk: Boolean = false, forceUpdate: Boolean = false): T { 328 return getInitializedValue( 329 observe = { observer -> 330 observeStale(ForeverActiveLifecycle, observer) 331 if (forceUpdate) { 332 updateIfActive() 333 } 334 }, 335 isInitialized = { isInitialized && (staleOk || !isStale) }) 336 } 337 338 /** 339 * A [Lifecycle]/[LifecycleOwner] that is permanently [State.STARTED] 340 * 341 * Passing this to [LiveData.observe] is essentially equivalent to using 342 * [LiveData.observeForever], so you have to make sure you handle your own cleanup whenever 343 * using this. 344 */ 345 private object ForeverActiveLifecycle : Lifecycle(), LifecycleOwner { 346 347 override fun getLifecycle(): Lifecycle = this 348 349 override fun addObserver(observer: LifecycleObserver) {} 350 351 override fun removeObserver(observer: LifecycleObserver) {} 352 353 override fun getCurrentState(): State = State.STARTED 354 } 355 }