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.permissioncontroller.PermissionControllerApplication
25 import java.util.concurrent.TimeUnit
26
27 /**
28 * A generalize data repository, which carries a component callback which trims its data in response
29 * to memory pressure
30 */
31 abstract class DataRepository<K, V : DataRepository.InactiveTimekeeper> : ComponentCallbacks2 {
32
33 /**
34 * Deadlines for removal based on memory pressure. Live Data objects which have been inactive
35 * for longer than the deadline will be removed.
36 */
37 private val TIME_THRESHOLD_LAX_NANOS: Long = TimeUnit.NANOSECONDS.convert(5, TimeUnit.MINUTES)
38 private val TIME_THRESHOLD_TIGHT_NANOS: Long = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MINUTES)
39 private val TIME_THRESHOLD_ALL_NANOS: Long = 0
40
41 protected val lock = Any()
42 @GuardedBy("lock")
43 protected val data = mutableMapOf<K, V>()
44
45 /**
46 * Whether or not this data repository has been registered as a component callback yet
47 */
48 private var registered = false
49 /**
50 * Whether or not this device is a low-RAM device.
51 */
52 private var isLowMemoryDevice = PermissionControllerApplication.get().getSystemService(
53 ActivityManager::class.java)?.isLowRamDevice ?: false
54
55 init {
56 PermissionControllerApplication.get().registerComponentCallbacks(this)
57 }
58
59 /**
60 * Get a value from this repository, creating it if needed
61 *
62 * @param key The key associated with the desired Value
63 *
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 *
77 * @return The generated Value
78 */
79 @MainThread
80 protected abstract fun newValue(key: K): V
81
82 /**
83 * Remove LiveData objects with no observer based on the severity of the memory pressure. If
84 * this is a low RAM device, eject all caches always, including upon the UI closing.
85 *
86 * @param level The severity of the current memory pressure
87 */
88 override fun onTrimMemory(level: Int) {
89 if (isLowMemoryDevice) {
90 trimInactiveData(TIME_THRESHOLD_ALL_NANOS)
91 return
92 }
93
94 trimInactiveData(threshold = when (level) {
95 ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> TIME_THRESHOLD_LAX_NANOS
96 ComponentCallbacks2.TRIM_MEMORY_MODERATE -> TIME_THRESHOLD_TIGHT_NANOS
97 ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> TIME_THRESHOLD_ALL_NANOS
98 ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> TIME_THRESHOLD_LAX_NANOS
99 ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> TIME_THRESHOLD_TIGHT_NANOS
100 ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> TIME_THRESHOLD_ALL_NANOS
101 else -> return
102 })
103 }
104
105 override fun onLowMemory() {
106 onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
107 }
108
109 override fun onConfigurationChanged(newConfig: Configuration) {
110 // Do nothing, but required to override by interface
111 }
112
113 fun invalidateSingle(key: K) {
114 synchronized(lock) {
115 data.remove(key)
116 }
117 }
118
119 private fun trimInactiveData(threshold: Long) {
120 synchronized(lock) {
121 data.keys.toList().forEach { key ->
122 if (data[key]?.timeInactive?.let { it >= threshold } == true) {
123 data.remove(key)
124 }
125 }
126 }
127 }
128
129 /**
130 * Interface which describes an object which can track how long it has been inactive, and if
131 * it has any observers.
132 */
133 interface InactiveTimekeeper {
134
135 /**
136 * Long value representing the time this object went inactive, which is read only on the
137 * main thread, so does not cause race conditions.
138 */
139 var timeWentInactive: Long?
140
141 /**
142 * Calculates the time since this object went inactive.
143 *
144 * @return The time since this object went inactive, or null if it is not inactive
145 */
146 val timeInactive: Long?
147 get() {
148 val time = timeWentInactive ?: return null
149 return System.nanoTime() - time
150 }
151 }
152 }
153
154 /**
155 * A DataRepository where all values are contingent on the existence of a package. Supports
156 * invalidating all values tied to a package. Expects key to be a pair or triple, with the package
157 * name as the first value of the key.
158 */
159 abstract class DataRepositoryForPackage<K, V : DataRepository.InactiveTimekeeper>
160 : DataRepository<K, V>() {
161
162 /**
163 * Invalidates every value with the packageName in the key.
164 *
165 * @param packageName The package to be invalidated
166 */
invalidateAllForPackagenull167 fun invalidateAllForPackage(packageName: String) {
168 synchronized(lock) {
169 for (key in data.keys.toSet()) {
170 if (key is Pair<*, *> || key is Triple<*, *, *> && key.first == packageName) {
171 data.remove(key)
172 }
173 }
174 }
175 }
176 }
177
178 /**
179 * A convenience to retrieve data from a repository with a composite key
180 */
getnull181 operator fun <K1, K2, V : DataRepository.InactiveTimekeeper> DataRepository<Pair<K1, K2>, V>.get(
182 k1: K1,
183 k2: K2
184 ): V {
185 return get(k1 to k2)
186 }
187
188 /**
189 * A convenience to retrieve data from a repository with a composite key
190 */
191 operator fun <K1, K2, K3, V : DataRepository.InactiveTimekeeper>
getnull192 DataRepository<Triple<K1, K2, K3>, V>.get(
193 k1: K1,
194 k2: K2,
195 k3: K3
196 ): V {
197 return get(Triple(k1, k2, k3))
198 }
199