• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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