1 /* <lambda>null2 * Copyright (C) 2024 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.settings.fuelgauge.batteryusage 18 19 import android.content.Context 20 import android.content.SharedPreferences 21 import android.util.ArrayMap 22 import android.util.Base64 23 import android.util.Log 24 import androidx.annotation.VisibleForTesting 25 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action 26 import com.android.settings.fuelgauge.BatteryOptimizeUtils 27 import com.android.settings.fuelgauge.BatteryUtils 28 29 /** A util to store and update app optimization mode expiration event data. */ 30 object AppOptModeSharedPreferencesUtils { 31 private const val TAG: String = "AppOptModeSharedPreferencesUtils" 32 private const val SHARED_PREFS_FILE: String = "app_optimization_mode_shared_prefs" 33 34 @VisibleForTesting const val UNLIMITED_EXPIRE_TIME: Long = -1L 35 36 private val appOptimizationModeLock = Any() 37 private val defaultInstance = AppOptimizationModeEvent.getDefaultInstance() 38 39 /** Returns all app optimization mode events for log. */ 40 @JvmStatic 41 fun getAllEvents(context: Context): List<AppOptimizationModeEvent> = 42 synchronized(appOptimizationModeLock) { getAppOptModeEventsMap(context).values.toList() } 43 44 /** Removes all app optimization mode events. */ 45 @JvmStatic 46 fun clearAll(context: Context) = 47 synchronized(appOptimizationModeLock) { 48 getSharedPreferences(context).edit().clear().apply() 49 } 50 51 /** Updates the app optimization mode event data. */ 52 @JvmStatic 53 fun updateAppOptModeExpiration( 54 context: Context, 55 uids: List<Int>, 56 packageNames: List<String>, 57 optimizationModes: List<Int>, 58 expirationTimes: LongArray, 59 ) = 60 // The internal fun with an additional lambda parameter is used to 61 // 1) get true BatteryOptimizeUtils in production environment 62 // 2) get fake BatteryOptimizeUtils for testing environment 63 updateAppOptModeExpirationInternal( 64 context, 65 uids, 66 packageNames, 67 optimizationModes, 68 expirationTimes, 69 ) { uid: Int, packageName: String -> 70 BatteryOptimizeUtils(context, uid, packageName) 71 } 72 73 /** Resets the app optimization mode event data since the query timestamp. */ 74 @JvmStatic 75 fun resetExpiredAppOptModeBeforeTimestamp(context: Context, queryTimestampMs: Long) = 76 synchronized(appOptimizationModeLock) { 77 val eventsMap = getAppOptModeEventsMap(context) 78 val expirationUids = ArrayList<Int>(eventsMap.size) 79 for ((uid, event) in eventsMap) { 80 if (event.expirationTime > queryTimestampMs) { 81 continue 82 } 83 updateBatteryOptimizationMode( 84 context, 85 event.uid, 86 event.packageName, 87 event.resetOptimizationMode, 88 Action.EXPIRATION_RESET, 89 ) 90 expirationUids.add(uid) 91 } 92 // Remove the expired AppOptimizationModeEvent data from storage 93 clearSharedPreferences(context, expirationUids) 94 } 95 96 /** Deletes all app optimization mode event data with a specific uid. */ 97 @JvmStatic 98 fun deleteAppOptimizationModeEventByUid(context: Context, uid: Int) = 99 synchronized(appOptimizationModeLock) { clearSharedPreferences(context, listOf(uid)) } 100 101 @VisibleForTesting 102 fun updateAppOptModeExpirationInternal( 103 context: Context, 104 uids: List<Int>, 105 packageNames: List<String>, 106 optimizationModes: List<Int>, 107 expirationTimes: LongArray, 108 getBatteryOptimizeUtils: (Int, String) -> BatteryOptimizeUtils, 109 ) = 110 synchronized(appOptimizationModeLock) { 111 val eventsMap = getAppOptModeEventsMap(context) 112 val expirationEvents: MutableMap<Int, AppOptimizationModeEvent> = ArrayMap() 113 for (i in uids.indices) { 114 val uid = uids[i] 115 val packageName = packageNames[i] 116 val optimizationMode = optimizationModes[i] 117 val originalOptMode: Int = 118 updateBatteryOptimizationMode( 119 context, 120 uid, 121 packageName, 122 optimizationMode, 123 Action.EXTERNAL_UPDATE, 124 getBatteryOptimizeUtils(uid, packageName), 125 ) 126 if (originalOptMode == BatteryOptimizeUtils.MODE_UNKNOWN) { 127 continue 128 } 129 // Make sure the reset mode is consistent with the expiration event in storage. 130 val resetOptMode = eventsMap[uid]?.resetOptimizationMode ?: originalOptMode 131 val expireTimeMs: Long = expirationTimes[i] 132 if (expireTimeMs != UNLIMITED_EXPIRE_TIME) { 133 Log.d( 134 TAG, 135 "setOptimizationMode($packageName) from $originalOptMode " + 136 "to $optimizationMode with expiration time $expireTimeMs", 137 ) 138 expirationEvents[uid] = 139 AppOptimizationModeEvent.newBuilder() 140 .setUid(uid) 141 .setPackageName(packageName) 142 .setResetOptimizationMode(resetOptMode) 143 .setExpirationTime(expireTimeMs) 144 .build() 145 } 146 } 147 148 // Append and update the AppOptimizationModeEvent. 149 if (expirationEvents.isNotEmpty()) { 150 updateSharedPreferences(context, expirationEvents) 151 } 152 } 153 154 @VisibleForTesting 155 fun updateBatteryOptimizationMode( 156 context: Context, 157 uid: Int, 158 packageName: String, 159 optimizationMode: Int, 160 action: Action, 161 batteryOptimizeUtils: BatteryOptimizeUtils = 162 BatteryOptimizeUtils(context, uid, packageName), 163 ): Int { 164 if (!batteryOptimizeUtils.isOptimizeModeMutable) { 165 Log.w(TAG, "Fail to update immutable optimization mode for: $packageName") 166 return BatteryOptimizeUtils.MODE_UNKNOWN 167 } 168 val currentOptMode = batteryOptimizeUtils.appOptimizationMode 169 batteryOptimizeUtils.setAppUsageState(optimizationMode, action) 170 Log.d( 171 TAG, 172 "setAppUsageState($packageName) to $optimizationMode with action = ${action.name}", 173 ) 174 return currentOptMode 175 } 176 177 private fun getSharedPreferences(context: Context): SharedPreferences { 178 return context.applicationContext.getSharedPreferences( 179 SHARED_PREFS_FILE, 180 Context.MODE_PRIVATE, 181 ) 182 } 183 184 private fun getAppOptModeEventsMap(context: Context): ArrayMap<Int, AppOptimizationModeEvent> { 185 val sharedPreferences = getSharedPreferences(context) 186 val allKeys = sharedPreferences.all?.keys ?: emptySet() 187 if (allKeys.isEmpty()) { 188 return ArrayMap() 189 } 190 val eventsMap = ArrayMap<Int, AppOptimizationModeEvent>(allKeys.size) 191 for (key in allKeys) { 192 sharedPreferences.getString(key, null)?.let { 193 eventsMap[key.toInt()] = deserializeAppOptimizationModeEvent(it) 194 } 195 } 196 return eventsMap 197 } 198 199 private fun updateSharedPreferences( 200 context: Context, 201 eventsMap: Map<Int, AppOptimizationModeEvent>, 202 ) { 203 val sharedPreferences = getSharedPreferences(context) 204 sharedPreferences.edit().run { 205 for ((uid, event) in eventsMap) { 206 putString(uid.toString(), serializeAppOptimizationModeEvent(event)) 207 } 208 apply() 209 } 210 } 211 212 private fun clearSharedPreferences(context: Context, uids: List<Int>) { 213 val sharedPreferences = getSharedPreferences(context) 214 sharedPreferences.edit().run { 215 for (uid in uids) { 216 remove(uid.toString()) 217 } 218 apply() 219 } 220 } 221 222 private fun serializeAppOptimizationModeEvent(event: AppOptimizationModeEvent): String { 223 return Base64.encodeToString(event.toByteArray(), Base64.DEFAULT) 224 } 225 226 private fun deserializeAppOptimizationModeEvent( 227 encodedProtoString: String, 228 ): AppOptimizationModeEvent { 229 return BatteryUtils.parseProtoFromString(encodedProtoString, defaultInstance) 230 } 231 } 232