• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }