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
diff --git a/Android.bp b/Android.bp
index 06ce8ab..b96d0dc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -95,15 +95,11 @@
"SettingsLibActivityEmbedding",
"aconfig_settings_flags_lib",
"accessibility_settings_flags_lib",
- "app-usage-event-protos-lite",
- "battery-event-protos-lite",
- "battery-usage-slot-protos-lite",
"contextualcards",
"development_settings_flag_lib",
"factory_reset_flags_lib",
"fuelgauge-log-protos-lite",
- "fuelgauge-usage-state-protos-lite",
- "power-anomaly-event-protos-lite",
+ "fuelgauge-protos-lite",
"settings-contextual-card-protos-lite",
"settings-log-bridge-protos-lite",
"settings-logtags",
diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto
index b16958d..3be173e 100644
--- a/protos/fuelgauge_log.proto
+++ b/protos/fuelgauge_log.proto
@@ -21,6 +21,7 @@
BACKUP = 5;
FORCE_RESET = 6;
EXTERNAL_UPDATE = 7;
+ EXPIRATION_RESET = 8;
}
optional string package_name = 1;
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index 42e6d9c..005c073 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -43,6 +43,7 @@
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 @@
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,
diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
index 9c7f007..3e37618 100644
--- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
@@ -182,6 +182,14 @@
&& 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 @@
}
}
- 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);
diff --git a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java
index b662d3e..2d2c838 100644
--- a/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/PowerBackgroundUsageDetail.java
@@ -35,6 +35,7 @@
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 @@
mExecutor.execute(
() -> {
+ if (currentOptimizeMode != mOptimizationMode) {
+ AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(
+ getContext(), mBatteryOptimizeUtils.getUid());
+ }
BatteryOptimizeLogUtils.writeLog(
getContext().getApplicationContext(),
Action.LEAVE,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt b/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt
new file mode 100644
index 0000000..60db031
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtils.kt
@@ -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)
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index 26bb6dd..0836912 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -167,6 +167,8 @@
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 =
diff --git a/src/com/android/settings/fuelgauge/protos/Android.bp b/src/com/android/settings/fuelgauge/protos/Android.bp
index 462962b..40fb987 100644
--- a/src/com/android/settings/fuelgauge/protos/Android.bp
+++ b/src/com/android/settings/fuelgauge/protos/Android.bp
@@ -9,41 +9,9 @@
}
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"],
}
diff --git a/src/com/android/settings/fuelgauge/protos/app_optimization_mode_event.proto b/src/com/android/settings/fuelgauge/protos/app_optimization_mode_event.proto
new file mode 100644
index 0000000..81d51bc
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/protos/app_optimization_mode_event.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;
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt
new file mode 100644
index 0000000..02d3739
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppOptModeSharedPreferencesUtilsTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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 androidx.test.core.app.ApplicationProvider
+import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action
+import com.android.settings.fuelgauge.BatteryOptimizeUtils
+import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_OPTIMIZED
+import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED
+import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNKNOWN
+import com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED
+import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils.UNLIMITED_EXPIRE_TIME
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class AppOptModeSharedPreferencesUtilsTest {
+ @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Spy private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Spy
+ private var testBatteryOptimizeUtils = spy(BatteryOptimizeUtils(context, UID, PACKAGE_NAME))
+
+ @Before
+ fun setup() {
+ AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(context, UID)
+ }
+
+ @Test
+ fun getAllEvents_emptyData_verifyEmptyList() {
+ assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty()
+ }
+
+ @Test
+ fun updateAppOptModeExpirationInternal_withExpirationTime_verifyData() {
+ insertAppOptModeEventForTest(/* expirationTime= */ 1000L)
+
+ val events = AppOptModeSharedPreferencesUtils.getAllEvents(context)
+
+ assertThat(events.size).isEqualTo(1)
+ assertAppOptimizationModeEventInfo(events.get(0), UID, PACKAGE_NAME, MODE_OPTIMIZED, 1000L)
+ }
+
+ @Test
+ fun updateAppOptModeExpirationInternal_withoutExpirationTime_verifyEmptyList() {
+ insertAppOptModeEventForTest(/* expirationTime= */ UNLIMITED_EXPIRE_TIME)
+
+ assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty()
+ }
+
+ @Test
+ fun deleteAppOptimizationModeEventByUid_uidNotContained_verifyData() {
+ insertAppOptModeEventForTest(/* expirationTime= */ 1000L)
+ assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context).size).isEqualTo(1)
+
+ AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(context, UNSET_UID)
+ val events = AppOptModeSharedPreferencesUtils.getAllEvents(context)
+
+ assertThat(events.size).isEqualTo(1)
+ assertAppOptimizationModeEventInfo(events.get(0), UID, PACKAGE_NAME, MODE_OPTIMIZED, 1000L)
+ }
+
+ @Test
+ fun deleteAppOptimizationModeEventByUid_uidExisting_verifyData() {
+ insertAppOptModeEventForTest(/* expirationTime= */ 1000L)
+
+ AppOptModeSharedPreferencesUtils.deleteAppOptimizationModeEventByUid(context, UID)
+
+ assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty()
+ }
+
+ @Test
+ fun resetExpiredAppOptModeBeforeTimestamp_noExpiredData_verifyData() {
+ insertAppOptModeEventForTest(/* expirationTime= */ 1000L)
+
+ AppOptModeSharedPreferencesUtils.resetExpiredAppOptModeBeforeTimestamp(context, 999L)
+ val events = AppOptModeSharedPreferencesUtils.getAllEvents(context)
+
+ assertThat(events.size).isEqualTo(1)
+ assertAppOptimizationModeEventInfo(events.get(0), UID, PACKAGE_NAME, MODE_OPTIMIZED, 1000L)
+ }
+
+ @Test
+ fun resetExpiredAppOptModeBeforeTimestamp_hasExpiredData_verifyEmptyList() {
+ insertAppOptModeEventForTest(/* expirationTime= */ 1000L)
+
+ AppOptModeSharedPreferencesUtils.resetExpiredAppOptModeBeforeTimestamp(context, 1001L)
+
+ assertThat(AppOptModeSharedPreferencesUtils.getAllEvents(context)).isEmpty()
+ }
+
+ @Test
+ fun updateBatteryOptimizationMode_updateToOptimizedMode_verifyAction() {
+ whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(true)
+ whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true))
+ .thenReturn(MODE_UNRESTRICTED)
+
+ val currentOptMode =
+ AppOptModeSharedPreferencesUtils.updateBatteryOptimizationMode(
+ context,
+ UID,
+ PACKAGE_NAME,
+ MODE_OPTIMIZED,
+ Action.EXTERNAL_UPDATE,
+ testBatteryOptimizeUtils
+ )
+
+ verify(testBatteryOptimizeUtils)?.setAppUsageState(MODE_OPTIMIZED, Action.EXTERNAL_UPDATE)
+ assertThat(currentOptMode).isEqualTo(MODE_UNRESTRICTED)
+ }
+
+ @Test
+ fun updateBatteryOptimizationMode_optimizationModeNotChanged_verifyAction() {
+ whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(false)
+ whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true))
+ .thenReturn(MODE_UNRESTRICTED)
+
+ val currentOptMode =
+ AppOptModeSharedPreferencesUtils.updateBatteryOptimizationMode(
+ context,
+ UID,
+ PACKAGE_NAME,
+ MODE_OPTIMIZED,
+ Action.EXTERNAL_UPDATE,
+ testBatteryOptimizeUtils
+ )
+
+ verify(testBatteryOptimizeUtils, never())?.setAppUsageState(anyInt(), any())
+ assertThat(currentOptMode).isEqualTo(MODE_UNKNOWN)
+ }
+
+ @Test
+ fun updateBatteryOptimizationMode_updateToSameOptimizationMode_verifyAction() {
+ whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(true)
+ whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)).thenReturn(MODE_RESTRICTED)
+
+ val currentOptMode =
+ AppOptModeSharedPreferencesUtils.updateBatteryOptimizationMode(
+ context,
+ UID,
+ PACKAGE_NAME,
+ MODE_RESTRICTED,
+ Action.EXTERNAL_UPDATE,
+ testBatteryOptimizeUtils
+ )
+
+ verify(testBatteryOptimizeUtils)?.setAppUsageState(MODE_RESTRICTED, Action.EXTERNAL_UPDATE)
+ assertThat(currentOptMode).isEqualTo(MODE_RESTRICTED)
+ }
+
+ private fun insertAppOptModeEventForTest(expirationTime: Long) {
+ whenever(testBatteryOptimizeUtils?.isOptimizeModeMutable).thenReturn(true)
+ whenever(testBatteryOptimizeUtils?.getAppOptimizationMode(true)).thenReturn(MODE_OPTIMIZED)
+ AppOptModeSharedPreferencesUtils.updateAppOptModeExpirationInternal(
+ context,
+ mutableListOf(UID),
+ mutableListOf(PACKAGE_NAME),
+ mutableListOf(MODE_OPTIMIZED),
+ longArrayOf(expirationTime)
+ ) { _: Int, _: String ->
+ testBatteryOptimizeUtils
+ }
+ }
+
+ companion object {
+ const val UID: Int = 12345
+ const val UNSET_UID: Int = 15432
+ const val PACKAGE_NAME: String = "com.android.app"
+
+ private fun assertAppOptimizationModeEventInfo(
+ event: AppOptimizationModeEvent,
+ uid: Int,
+ packageName: String,
+ resetOptimizationMode: Int,
+ expirationTime: Long
+ ) {
+ assertThat(event.uid).isEqualTo(uid)
+ assertThat(event.packageName).isEqualTo(packageName)
+ assertThat(event.resetOptimizationMode).isEqualTo(resetOptimizationMode)
+ assertThat(event.expirationTime).isEqualTo(expirationTime)
+ }
+ }
+}