1 /* 2 * 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 */ 167 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 */ 181 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> 192 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