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