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