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