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.app.ActivityManager
20 import android.content.ComponentCallbacks2
21 import android.content.res.Configuration
22 import androidx.annotation.GuardedBy
23 import androidx.annotation.MainThread
24 import com.android.modules.utils.build.SdkLevel
25 import com.android.permissioncontroller.PermissionControllerApplication
26 import com.android.permissioncontroller.permission.utils.ContextCompat
27 import com.android.permissioncontroller.permission.utils.KotlinUtils
28 import java.util.concurrent.TimeUnit
29 
30 /**
31  * A generalize data repository, which carries a component callback which trims its data in response
32  * to memory pressure
33  */
34 abstract class DataRepository<K, V : DataRepository.InactiveTimekeeper> : ComponentCallbacks2 {
35 
36     /**
37      * Deadlines for removal based on memory pressure. Live Data objects which have been inactive
38      * for longer than the deadline will be removed.
39      */
40     private val TIME_THRESHOLD_LAX_NANOS: Long = TimeUnit.NANOSECONDS.convert(5, TimeUnit.MINUTES)
41     private val TIME_THRESHOLD_TIGHT_NANOS: Long = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MINUTES)
42     private val TIME_THRESHOLD_ALL_NANOS: Long = 0
43 
44     protected val lock = Any()
45     @GuardedBy("lock") protected val data = mutableMapOf<K, V>()
46 
47     /** Whether or not this data repository has been registered as a component callback yet */
48     private var registered = false
49     /** Whether or not this device is a low-RAM device. */
50     private var isLowMemoryDevice =
51         PermissionControllerApplication.get()
52             .getSystemService(ActivityManager::class.java)
53             ?.isLowRamDevice
54             ?: false
55 
56     init {
57         PermissionControllerApplication.get().registerComponentCallbacks(this)
58     }
59 
60     /**
61      * Get a value from this repository, creating it if needed
62      *
63      * @param key The key associated with the desired Value
64      * @return The cached or newly created Value for the given Key
65      */
66     operator fun get(key: K): V {
67         synchronized(lock) {
68             return data.getOrPut(key) { newValue(key) }
69         }
70     }
71 
72     /**
73      * Generate a new value type from the given data
74      *
75      * @param key Information about this value object, used to instantiate it
76      * @return The generated Value
77      */
78     @MainThread protected abstract fun newValue(key: K): V
79 
80     /**
81      * Remove LiveData objects with no observer.
82      */
83     override fun onTrimMemory(level: Int) {
84         if (isLowMemoryDevice) {
85             trimInactiveData(TIME_THRESHOLD_ALL_NANOS)
86             return
87         }
88 
89         if (SdkLevel.isAtLeastU() && level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
90             // On UDC+ TRIM_MEMORY_BACKGROUND may be the last callback an app will receive
91             // before it's frozen.
92             trimInactiveData(TIME_THRESHOLD_ALL_NANOS)
93             return
94         }
95 
96         trimInactiveData(
97             threshold =
98                 when (level) {
99                     ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> TIME_THRESHOLD_LAX_NANOS
100                     // Allow handling for trim levels that are deprecated in newer API versions
101                     // but are still supported on older devices that this code ships to.
102                     @Suppress("DEPRECATION") ComponentCallbacks2.TRIM_MEMORY_MODERATE -> TIME_THRESHOLD_TIGHT_NANOS
103                     @Suppress("DEPRECATION") ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> TIME_THRESHOLD_ALL_NANOS
104                     @Suppress("DEPRECATION") ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> TIME_THRESHOLD_LAX_NANOS
105                     @Suppress("DEPRECATION") ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> TIME_THRESHOLD_TIGHT_NANOS
106                     @Suppress("DEPRECATION") ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> TIME_THRESHOLD_ALL_NANOS
107                     else -> return
108                 }
109         )
110     }
111 
112     // Allow handling for trim levels that are deprecated in newer API versions
113     // but are still supported on older devices that this code ships to.
114     override fun onLowMemory() {
115         onTrimMemory(@Suppress("DEPRECATION") ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
116     }
117 
118     override fun onConfigurationChanged(newConfig: Configuration) {
119         // Do nothing, but required to override by interface
120     }
121 
122     fun invalidateSingle(key: K) {
123         synchronized(lock) { data.remove(key) }
124     }
125 
126     private fun trimInactiveData(threshold: Long) {
127         synchronized(lock) {
128             data.keys.toList().forEach { key ->
129                 if (data[key]?.timeInactive?.let { it >= threshold } == true) {
130                     data.remove(key)
131                 }
132             }
133         }
134     }
135 
136     /**
137      * Interface which describes an object which can track how long it has been inactive, and if it
138      * has any observers.
139      */
140     interface InactiveTimekeeper {
141 
142         /**
143          * Long value representing the time this object went inactive, which is read only on the
144          * main thread, so does not cause race conditions.
145          */
146         var timeWentInactive: Long?
147 
148         /**
149          * Calculates the time since this object went inactive.
150          *
151          * @return The time since this object went inactive, or null if it is not inactive
152          */
153         val timeInactive: Long?
154             get() {
155                 val time = timeWentInactive ?: return null
156                 return System.nanoTime() - time
157             }
158     }
159 }
160 
161 /**
162  * A DataRepository where all values are contingent on the existence of a package. Supports
163  * invalidating all values tied to a package. Expects key to be a pair or triple, with the package
164  * name as the first value of the key.
165  */
166 abstract class DataRepositoryForPackage<K, V : DataRepository.InactiveTimekeeper> :
167     DataRepository<K, V>() {
168 
169     /**
170      * Invalidates every value with the packageName in the key.
171      *
172      * @param packageName The package to be invalidated
173      */
invalidateAllForPackagenull174     fun invalidateAllForPackage(packageName: String) {
175         synchronized(lock) {
176             for (key in data.keys.toSet()) {
177                 if (
178                     key is Pair<*, *> ||
179                         key is Triple<*, *, *> ||
180                         key is KotlinUtils.Quadruple<*, *, *, *> && key.first == packageName
181                 ) {
182                     data.remove(key)
183                 }
184             }
185         }
186     }
187 }
188 
189 /**
190  * A DataRepository to cache LiveData for a device. The device can be a primary device with default
191  * deviceId in the key, or a remote device with virtual device Id in the key. It uses deviceId to
192  * initialize a new LiveData instance. Note: the virtual device Id should always be the last element
193  * in the composite key.
194  */
195 abstract class DataRepositoryForDevice<K, V : DataRepository.InactiveTimekeeper> :
196     DataRepositoryForPackage<K, V>() {
197 
newValuenull198     @MainThread protected abstract fun newValue(key: K, deviceId: Int): V
199 
200     override fun newValue(key: K): V {
201         return newValue(key, ContextCompat.DEVICE_ID_DEFAULT)
202     }
203 
getWithDeviceIdnull204     fun getWithDeviceId(key: K, deviceId: Int): V {
205         synchronized(lock) {
206             return data.getOrPut(key) { newValue(key, deviceId) }
207         }
208     }
209 }
210 
211 /** A convenience to retrieve data from a repository with a composite key */
getnull212 operator fun <K1, K2, V : DataRepository.InactiveTimekeeper> DataRepository<Pair<K1, K2>, V>.get(
213     k1: K1,
214     k2: K2
215 ): V {
216     return get(k1 to k2)
217 }
218 
219 /** A convenience to retrieve data from a repository with a composite key */
220 operator fun <K1, K2, K3, V : DataRepository.InactiveTimekeeper> DataRepository<
221     Triple<K1, K2, K3>, V
222 >
getnull223     .get(k1: K1, k2: K2, k3: K3): V {
224     return get(Triple(k1, k2, k3))
225 }
226 
227 /** A getter on DataRepositoryForDevice to retrieve a LiveData for a device. */
228 operator fun <K1, K2, V : DataRepository.InactiveTimekeeper> DataRepositoryForDevice<
229     Triple<K1, K2, Int>, V
230 >
getnull231     .get(k1: K1, k2: K2, deviceId: Int): V {
232     return getWithDeviceId(Triple(k1, k2, deviceId), deviceId)
233 }
234 
235 /**
236  * A collection of getters on DataRepositoryForDevice to conveniently retrieve a LiveData for tbe
237  * primary device. The param can be in the format of Pair<K1, K2> or [K1, K2]
238  */
239 operator fun <K1, K2, V : DataRepository.InactiveTimekeeper> DataRepositoryForDevice<
240     Triple<K1, K2, Int>, V
241 >
getnull242     .get(
243     k1: K1,
244     k2: K2,
245 ): V {
246     return getWithDeviceId(
247         Triple(k1, k2, ContextCompat.DEVICE_ID_DEFAULT),
248         ContextCompat.DEVICE_ID_DEFAULT
249     )
250 }
251 
252 operator fun <K1, K2, V : DataRepository.InactiveTimekeeper> DataRepositoryForDevice<
253     Triple<K1, K2, Int>, V
254 >
getnull255     .get(key: Pair<K1, K2>): V {
256     return getWithDeviceId(
257         Triple(key.first, key.second, ContextCompat.DEVICE_ID_DEFAULT),
258         ContextCompat.DEVICE_ID_DEFAULT
259     )
260 }
261 
262 /** A getter on DataRepositoryForDevice to retrieve a LiveData for a device. */
263 operator fun <K1, K2, K3, V : DataRepository.InactiveTimekeeper> DataRepositoryForDevice<
264     KotlinUtils.Quadruple<K1, K2, K3, Int>, V
265 >
getnull266     .get(k1: K1, k2: K2, k3: K3, deviceId: Int): V {
267     return getWithDeviceId(KotlinUtils.Quadruple(k1, k2, k3, deviceId), deviceId)
268 }
269 
270 /**
271  * A collection of getters on DataRepositoryForDevice to conveniently retrieve a LiveData for tbe
272  * primary device. The param can be in the format of Triple<K1, K2, K3> or [K1, K2, K3]
273  */
274 operator fun <K1, K2, K3, V : DataRepository.InactiveTimekeeper> DataRepositoryForDevice<
275     KotlinUtils.Quadruple<K1, K2, K3, Int>, V
276 >
getnull277     .get(k1: K1, k2: K2, k3: K3): V {
278     return getWithDeviceId(
279         KotlinUtils.Quadruple(k1, k2, k3, ContextCompat.DEVICE_ID_DEFAULT),
280         ContextCompat.DEVICE_ID_DEFAULT
281     )
282 }
283 
284 operator fun <K1, K2, K3, V : DataRepository.InactiveTimekeeper> DataRepositoryForDevice<
285     KotlinUtils.Quadruple<K1, K2, K3, Int>, V
286 >
getnull287     .get(key: Triple<K1, K2, K3>): V {
288     return getWithDeviceId(
289         KotlinUtils.Quadruple(key.first, key.second, key.third, ContextCompat.DEVICE_ID_DEFAULT),
290         ContextCompat.DEVICE_ID_DEFAULT
291     )
292 }
293