Add AppOptimizationModeEventsUtils to save & update app optimization mode expiration events.
- [Update] Save app optimizaiton mode set & expire events from turbo. - [Reset ] Restore optimization mode for expired events in Periodic job. - [Delete] Cancel expiration event if user updates mode in app usage page. Bug: 338965652 Test: atest + manual Change-Id: I3fb7311207da1bdb1146ea1ff041aca6adb66052
This commit is contained in:
@@ -43,6 +43,7 @@ import com.android.settings.core.InstrumentedPreferenceFragment;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
|
||||
import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils;
|
||||
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
|
||||
import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
@@ -274,9 +275,12 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
|
||||
final int currentOptimizeMode = mBatteryOptimizeUtils.getAppOptimizationMode();
|
||||
mLogStringBuilder.append(", onPause mode = ").append(currentOptimizeMode);
|
||||
logMetricCategory(currentOptimizeMode);
|
||||
|
||||
mExecutor.execute(
|
||||
() -> {
|
||||
if (currentOptimizeMode != mOptimizationMode) {
|
||||
AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(
|
||||
getContext(), mBatteryOptimizeUtils.getUid());
|
||||
}
|
||||
BatteryOptimizeLogUtils.writeLog(
|
||||
getContext().getApplicationContext(),
|
||||
Action.LEAVE,
|
||||
|
||||
@@ -182,6 +182,14 @@ public class BatteryOptimizeUtils {
|
||||
&& getAppOptimizationMode() != BatteryOptimizeUtils.MODE_RESTRICTED;
|
||||
}
|
||||
|
||||
String getPackageName() {
|
||||
return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName;
|
||||
}
|
||||
|
||||
int getUid() {
|
||||
return mUid;
|
||||
}
|
||||
|
||||
/** Gets the list of installed applications. */
|
||||
public static ArraySet<ApplicationInfo> getInstalledApplications(
|
||||
Context context, IPackageManager ipm) {
|
||||
@@ -257,10 +265,6 @@ public class BatteryOptimizeUtils {
|
||||
}
|
||||
}
|
||||
|
||||
String getPackageName() {
|
||||
return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName;
|
||||
}
|
||||
|
||||
static int getMode(AppOpsManager appOpsManager, int uid, String packageName) {
|
||||
return appOpsManager.checkOpNoThrow(
|
||||
AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
|
||||
|
||||
@@ -35,6 +35,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settings.widget.EntityHeaderController;
|
||||
import com.android.settingslib.HelpUtils;
|
||||
@@ -121,6 +122,10 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
|
||||
|
||||
mExecutor.execute(
|
||||
() -> {
|
||||
if (currentOptimizeMode != mOptimizationMode) {
|
||||
AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(
|
||||
getContext(), mBatteryOptimizeUtils.getUid());
|
||||
}
|
||||
BatteryOptimizeLogUtils.writeLog(
|
||||
getContext().getApplicationContext(),
|
||||
Action.LEAVE,
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.fuelgauge.batteryusage
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.ArrayMap
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action
|
||||
import com.android.settings.fuelgauge.BatteryOptimizeUtils
|
||||
import com.android.settings.fuelgauge.BatteryUtils
|
||||
|
||||
/** A util to store and update app optimization mode expiration event data. */
|
||||
object AppOptModeSharedPreferencesUtils {
|
||||
private const val TAG: String = "AppOptModeSharedPreferencesUtils"
|
||||
private const val SHARED_PREFS_FILE: String = "app_optimization_mode_shared_prefs"
|
||||
|
||||
@VisibleForTesting const val UNLIMITED_EXPIRE_TIME: Long = -1L
|
||||
|
||||
private val appOptimizationModeLock = Any()
|
||||
private val defaultInstance = AppOptimizationModeEvent.getDefaultInstance()
|
||||
|
||||
/** Returns all app optimization mode events for log. */
|
||||
@JvmStatic
|
||||
fun getAllEvents(context: Context): List<AppOptimizationModeEvent> =
|
||||
synchronized(appOptimizationModeLock) { getAppOptModeEventsMap(context).values.toList() }
|
||||
|
||||
/** Updates the app optimization mode event data. */
|
||||
@JvmStatic
|
||||
fun updateAppOptModeExpiration(
|
||||
context: Context,
|
||||
uids: List<Int>,
|
||||
packageNames: List<String>,
|
||||
optimizationModes: List<Int>,
|
||||
expirationTimes: LongArray,
|
||||
) =
|
||||
// The internal fun with an additional lambda parameter is used to
|
||||
// 1) get true BatteryOptimizeUtils in production environment
|
||||
// 2) get fake BatteryOptimizeUtils for testing environment
|
||||
updateAppOptModeExpirationInternal(
|
||||
context,
|
||||
uids,
|
||||
packageNames,
|
||||
optimizationModes,
|
||||
expirationTimes
|
||||
) { uid: Int, packageName: String ->
|
||||
BatteryOptimizeUtils(context, uid, packageName)
|
||||
}
|
||||
|
||||
/** Resets the app optimization mode event data since the query timestamp. */
|
||||
@JvmStatic
|
||||
fun resetExpiredAppOptModeBeforeTimestamp(context: Context, queryTimestamp: Long) =
|
||||
synchronized(appOptimizationModeLock) {
|
||||
val eventsMap = getAppOptModeEventsMap(context)
|
||||
val expirationUids = ArrayList<Int>(eventsMap.size)
|
||||
for ((uid, event) in eventsMap) {
|
||||
if (event.expirationTime > queryTimestamp) {
|
||||
continue
|
||||
}
|
||||
updateBatteryOptimizationMode(
|
||||
context,
|
||||
event.uid,
|
||||
event.packageName,
|
||||
event.resetOptimizationMode,
|
||||
Action.EXPIRATION_RESET,
|
||||
)
|
||||
expirationUids.add(uid)
|
||||
}
|
||||
// Remove the expired AppOptimizationModeEvent data from storage
|
||||
clearSharedPreferences(context, expirationUids)
|
||||
}
|
||||
|
||||
/** Deletes all app optimization mode event data with a specific uid. */
|
||||
@JvmStatic
|
||||
fun deleteAppOptimizationModeEventByUid(context: Context, uid: Int) =
|
||||
synchronized(appOptimizationModeLock) { clearSharedPreferences(context, listOf(uid)) }
|
||||
|
||||
@VisibleForTesting
|
||||
fun updateAppOptModeExpirationInternal(
|
||||
context: Context,
|
||||
uids: List<Int>,
|
||||
packageNames: List<String>,
|
||||
optimizationModes: List<Int>,
|
||||
expirationTimes: LongArray,
|
||||
getBatteryOptimizeUtils: (Int, String) -> BatteryOptimizeUtils
|
||||
) =
|
||||
synchronized(appOptimizationModeLock) {
|
||||
val eventsMap = getAppOptModeEventsMap(context)
|
||||
val expirationEvents: MutableMap<Int, AppOptimizationModeEvent> = ArrayMap()
|
||||
for (i in uids.indices) {
|
||||
val uid = uids[i]
|
||||
val packageName = packageNames[i]
|
||||
val optimizationMode = optimizationModes[i]
|
||||
val originalOptMode: Int =
|
||||
updateBatteryOptimizationMode(
|
||||
context,
|
||||
uid,
|
||||
packageName,
|
||||
optimizationMode,
|
||||
Action.EXTERNAL_UPDATE,
|
||||
getBatteryOptimizeUtils(uid, packageName)
|
||||
)
|
||||
if (originalOptMode == BatteryOptimizeUtils.MODE_UNKNOWN) {
|
||||
continue
|
||||
}
|
||||
// Make sure the reset mode is consistent with the expiration event in storage.
|
||||
val resetOptMode = eventsMap[uid]?.resetOptimizationMode ?: originalOptMode
|
||||
val expireTimeMs: Long = expirationTimes[i]
|
||||
if (expireTimeMs != UNLIMITED_EXPIRE_TIME) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"setOptimizationMode($packageName) from $originalOptMode " +
|
||||
"to $optimizationMode with expiration time $expireTimeMs",
|
||||
)
|
||||
expirationEvents[uid] =
|
||||
AppOptimizationModeEvent.newBuilder()
|
||||
.setUid(uid)
|
||||
.setPackageName(packageName)
|
||||
.setResetOptimizationMode(resetOptMode)
|
||||
.setExpirationTime(expireTimeMs)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
// Append and update the AppOptimizationModeEvent.
|
||||
if (expirationEvents.isNotEmpty()) {
|
||||
updateSharedPreferences(context, expirationEvents)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun updateBatteryOptimizationMode(
|
||||
context: Context,
|
||||
uid: Int,
|
||||
packageName: String,
|
||||
optimizationMode: Int,
|
||||
action: Action,
|
||||
batteryOptimizeUtils: BatteryOptimizeUtils = BatteryOptimizeUtils(context, uid, packageName)
|
||||
): Int {
|
||||
if (!batteryOptimizeUtils.isOptimizeModeMutable) {
|
||||
Log.w(TAG, "Fail to update immutable optimization mode for: $packageName")
|
||||
return BatteryOptimizeUtils.MODE_UNKNOWN
|
||||
}
|
||||
val currentOptMode = batteryOptimizeUtils.appOptimizationMode
|
||||
batteryOptimizeUtils.setAppUsageState(optimizationMode, action)
|
||||
Log.d(
|
||||
TAG,
|
||||
"setAppUsageState($packageName) to $optimizationMode with action = ${action.name}",
|
||||
)
|
||||
return currentOptMode
|
||||
}
|
||||
|
||||
private fun getSharedPreferences(context: Context): SharedPreferences {
|
||||
return context.applicationContext.getSharedPreferences(
|
||||
SHARED_PREFS_FILE,
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAppOptModeEventsMap(context: Context): ArrayMap<Int, AppOptimizationModeEvent> {
|
||||
val sharedPreferences = getSharedPreferences(context)
|
||||
val allKeys = sharedPreferences.all?.keys ?: emptySet()
|
||||
if (allKeys.isEmpty()) {
|
||||
return ArrayMap()
|
||||
}
|
||||
val eventsMap = ArrayMap<Int, AppOptimizationModeEvent>(allKeys.size)
|
||||
for (key in allKeys) {
|
||||
sharedPreferences.getString(key, null)?.let {
|
||||
eventsMap[key.toInt()] = deserializeAppOptimizationModeEvent(it)
|
||||
}
|
||||
}
|
||||
return eventsMap
|
||||
}
|
||||
|
||||
private fun updateSharedPreferences(
|
||||
context: Context,
|
||||
eventsMap: Map<Int, AppOptimizationModeEvent>
|
||||
) {
|
||||
val sharedPreferences = getSharedPreferences(context)
|
||||
sharedPreferences.edit().run {
|
||||
for ((uid, event) in eventsMap) {
|
||||
putString(uid.toString(), serializeAppOptimizationModeEvent(event))
|
||||
}
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearSharedPreferences(context: Context, uids: List<Int>) {
|
||||
val sharedPreferences = getSharedPreferences(context)
|
||||
sharedPreferences.edit().run {
|
||||
for (uid in uids) {
|
||||
remove(uid.toString())
|
||||
}
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun serializeAppOptimizationModeEvent(event: AppOptimizationModeEvent): String {
|
||||
return Base64.encodeToString(event.toByteArray(), Base64.DEFAULT)
|
||||
}
|
||||
|
||||
private fun deserializeAppOptimizationModeEvent(
|
||||
encodedProtoString: String
|
||||
): AppOptimizationModeEvent {
|
||||
return BatteryUtils.parseProtoFromString(encodedProtoString, defaultInstance)
|
||||
}
|
||||
}
|
||||
@@ -167,6 +167,8 @@ public final class BatteryUsageDataLoader {
|
||||
try {
|
||||
final long start = System.currentTimeMillis();
|
||||
loadBatteryStatsData(context, isFullChargeStart);
|
||||
AppOptModeSharedPreferencesUtils.resetExpiredAppOptModeBeforeTimestamp(
|
||||
context, System.currentTimeMillis());
|
||||
if (!isFullChargeStart) {
|
||||
// No app usage data or battery diff data at this time.
|
||||
final UserIdsSeries userIdsSeries =
|
||||
|
||||
@@ -9,41 +9,9 @@ package {
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "app-usage-event-protos-lite",
|
||||
name: "fuelgauge-protos-lite",
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["app_usage_event.proto"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "battery-event-protos-lite",
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["battery_event.proto"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "battery-usage-slot-protos-lite",
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["battery_usage_slot.proto"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "fuelgauge-usage-state-protos-lite",
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["fuelgauge_usage_state.proto"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "power-anomaly-event-protos-lite",
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["power_anomaly_event.proto"],
|
||||
srcs: ["*.proto"],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "com.android.settings.fuelgauge.batteryusage";
|
||||
option java_outer_classname = "AppOptimizationModeEventProto";
|
||||
|
||||
message AppOptimizationModeEvents {
|
||||
// Map of uid to AppOptimizationModeEvent
|
||||
map<int32, AppOptimizationModeEvent> events = 1;
|
||||
}
|
||||
|
||||
message AppOptimizationModeEvent {
|
||||
optional int32 uid = 1;
|
||||
optional string package_name = 2;
|
||||
// Value of BatteryUsageSlot.BatteryOptimizationMode, range = [0,3]
|
||||
optional int32 reset_optimization_mode = 3;
|
||||
optional int64 expiration_time = 4;
|
||||
}
|
||||
Reference in New Issue
Block a user