Record app optimization mode backup into BatteryHistoricalLog

App optimization mode format:
https://screenshot.googleplex.com/di9DDzBfYf7ihfV

App optimization mode backup format:
https://screenshot.googleplex.com/GkVW5HrgGvmv5yh

Bug: 192523697
Test: make SettingsRoboTests
Change-Id: I60a9a76a8ffc89d625ee3f77c138a19181c81c38
diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto
index cf87dc7..8512cb8 100644
--- a/protos/fuelgauge_log.proto
+++ b/protos/fuelgauge_log.proto
@@ -19,10 +19,11 @@
     APPLY = 2;
     RESET = 3;
     RESTORE = 4;
+    BACKUP = 5;
   }
 
   optional string package_name = 1;
   optional Action action = 2;
   optional string action_description = 3;
   optional int64 timestamp = 4;
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
index 0558d46..79df57a 100644
--- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
+++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
@@ -22,6 +22,7 @@
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupHelper;
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.os.IDeviceIdleController;
@@ -34,9 +35,11 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -47,6 +50,8 @@
     /** An inditifier for {@link BackupHelper}. */
     public static final String TAG = "BatteryBackupHelper";
     private static final String DEVICE_IDLE_SERVICE = "deviceidle";
+    private static final String BATTERY_OPTIMIZE_BACKUP_FILE_NAME =
+            "battery_optimize_backup_historical_logs";
 
     static final String DELIMITER = ",";
     static final String DELIMITER_MODE = ":";
@@ -141,6 +146,7 @@
         int backupCount = 0;
         final StringBuilder builder = new StringBuilder();
         final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+        final SharedPreferences sharedPreferences = getSharedPreferences(mContext);
         // Converts application into the AppUsageState.
         for (ApplicationInfo info : applications) {
             final int mode = BatteryOptimizeUtils.getMode(appOps, info.uid, info.packageName);
@@ -157,6 +163,9 @@
                     info.packageName + DELIMITER_MODE + optimizationMode;
             builder.append(packageOptimizeMode + DELIMITER);
             Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode);
+            BatteryHistoricalLogUtil.writeLog(
+                    sharedPreferences, Action.BACKUP, info.packageName,
+                    /* actionDescription */ "mode: " + optimizationMode);
             backupCount++;
         }
 
@@ -210,6 +219,18 @@
                 restoreCount, (System.currentTimeMillis() - timestamp)));
     }
 
+    /** Dump the app optimization mode backup history data. */
+    public static void dumpHistoricalData(Context context, PrintWriter writer) {
+        BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(
+                getSharedPreferences(context), writer);
+    }
+
+    @VisibleForTesting
+    static SharedPreferences getSharedPreferences(Context context) {
+        return context.getSharedPreferences(
+                BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE);
+    }
+
     private void restoreOptimizationMode(
             String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
         final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java b/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java
index a827e6d..f82b703 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtil.java
@@ -37,40 +37,40 @@
     @VisibleForTesting
     static final int MAX_ENTRIES = 40;
 
-    /**
-     * Writes a log entry.
-     *
-     * <p>Keeps up to {@link #MAX_ENTRIES} in the log, once that number is exceeded, it prunes the
-     * oldest one.
-     */
-    static void writeLog(Context context, Action action, String pkg, String actionDescription) {
+    /** Writes a log entry for battery optimization mode. */
+    static void writeLog(
+            Context context, Action action, String packageName, String actionDescription) {
+        writeLog(getSharedPreferences(context), action, packageName, actionDescription);
+    }
+
+    static void writeLog(SharedPreferences sharedPreferences, Action action,
+            String packageName, String actionDescription) {
         writeLog(
-                context,
+                sharedPreferences,
                 BatteryOptimizeHistoricalLogEntry.newBuilder()
-                        .setPackageName(pkg)
+                        .setPackageName(packageName)
                         .setAction(action)
                         .setActionDescription(actionDescription)
                         .setTimestamp(System.currentTimeMillis())
                         .build());
     }
 
-    private static void writeLog(Context context, BatteryOptimizeHistoricalLogEntry logEntry) {
-        SharedPreferences sharedPreferences = getSharedPreferences(context);
-
+    private static void writeLog(
+            SharedPreferences sharedPreferences, BatteryOptimizeHistoricalLogEntry logEntry) {
         BatteryOptimizeHistoricalLog existingLog =
                 parseLogFromString(sharedPreferences.getString(LOGS_KEY, ""));
         BatteryOptimizeHistoricalLog.Builder newLogBuilder = existingLog.toBuilder();
-        // Prune old entries
+        // Prune old entries to limit the max logging data count.
         if (existingLog.getLogEntryCount() >= MAX_ENTRIES) {
             newLogBuilder.removeLogEntry(0);
         }
         newLogBuilder.addLogEntry(logEntry);
 
+        String loggingContent =
+            Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT);
         sharedPreferences
                 .edit()
-                .putString(
-                        LOGS_KEY,
-                        Base64.encodeToString(newLogBuilder.build().toByteArray(), Base64.DEFAULT))
+                .putString(LOGS_KEY, loggingContent)
                 .apply();
     }
 
@@ -79,34 +79,36 @@
                 storedLogs, BatteryOptimizeHistoricalLog.getDefaultInstance());
     }
 
-    /**
-     * Prints the historical log that has previously been stored by this utility.
-     */
+    /** Prints the historical log that has previously been stored by this utility. */
     public static void printBatteryOptimizeHistoricalLog(Context context, PrintWriter writer) {
+        printBatteryOptimizeHistoricalLog(getSharedPreferences(context), writer);
+    }
+
+    /** Prints the historical log that has previously been stored by this utility. */
+    public static void printBatteryOptimizeHistoricalLog(
+            SharedPreferences sharedPreferences, PrintWriter writer) {
         writer.println("Battery optimize state history:");
-        SharedPreferences sharedPreferences = getSharedPreferences(context);
         BatteryOptimizeHistoricalLog existingLog =
                 parseLogFromString(sharedPreferences.getString(LOGS_KEY, ""));
         List<BatteryOptimizeHistoricalLogEntry> logEntryList = existingLog.getLogEntryList();
         if (logEntryList.isEmpty()) {
-            writer.println("\tNo past logs.");
+            writer.println("\tnothing to dump");
         } else {
-            writer.println("0:RESTRICTED  1:UNRESTRICTED  2:OPTIMIZED  3:UNKNOWN");
+            writer.println("0:UNKNOWN 1:RESTRICTED  2:UNRESTRICTED 3:OPTIMIZED");
             logEntryList.forEach(entry -> writer.println(toString(entry)));
         }
     }
 
-    /**
-     * Gets the unique key for logging, combined with package name, delimiter and user id.
-     */
-    static String getPackageNameWithUserId(String pkgName, int userId) {
-        return pkgName + ":" + userId;
+    /** Gets the unique key for logging. */
+    static String getPackageNameWithUserId(String packageName, int userId) {
+        return packageName + ":" + userId;
     }
 
     private static String toString(BatteryOptimizeHistoricalLogEntry entry) {
-        return String.format("%s\tAction:%s\tEvent:%s\tTimestamp:%s", entry.getPackageName(),
-                entry.getAction(), entry.getActionDescription(),
-                ConvertUtils.utcToLocalTimeForLogging(entry.getTimestamp()));
+        return String.format("%s\t%s\taction:%s\tevent:%s",
+                ConvertUtils.utcToLocalTimeForLogging(entry.getTimestamp()),
+                entry.getPackageName(), entry.getAction(),
+                entry.getActionDescription());
     }
 
     @VisibleForTesting
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
index f3d6816..8970730 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
@@ -70,6 +70,8 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
@@ -84,6 +86,8 @@
     private static final int UID1 = 1;
 
     private Context mContext;
+    private PrintWriter mPrintWriter;
+    private StringWriter mStringWriter;
     private BatteryBackupHelper mBatteryBackupHelper;
 
     @Mock
@@ -109,6 +113,8 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext = spy(RuntimeEnvironment.application);
+        mStringWriter = new StringWriter();
+        mPrintWriter = new PrintWriter(mStringWriter);
         doReturn(mContext).when(mContext).getApplicationContext();
         doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
         doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
@@ -126,6 +132,7 @@
     @After
     public void resetShadows() {
         ShadowUserHandle.reset();
+        BatteryBackupHelper.getSharedPreferences(mContext).edit().clear().apply();
     }
 
     @Test
@@ -216,6 +223,8 @@
         // 2 for UNRESTRICTED mode and 1 for RESTRICTED mode.
         final String expectedResult = PACKAGE_NAME1 + ":2," + PACKAGE_NAME2 + ":1,";
         verifyBackupData(expectedResult);
+        verifyDumpHistoryData("com.android.testing.1\taction:BACKUP\tevent:mode: 2");
+        verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1");
     }
 
     @Test
@@ -232,6 +241,7 @@
         // "com.android.testing.2" for RESTRICTED mode.
         final String expectedResult = PACKAGE_NAME2 + ":1,";
         verifyBackupData(expectedResult);
+        verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1");
     }
 
     @Test
@@ -248,6 +258,7 @@
         // "com.android.testing.2" for RESTRICTED mode.
         final String expectedResult = PACKAGE_NAME2 + ":1,";
         verifyBackupData(expectedResult);
+        verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1");
     }
 
     @Test
@@ -357,6 +368,11 @@
         doReturn(dataKey).when(mBackupDataInputStream).getKey();
     }
 
+    private void verifyDumpHistoryData(String expectedResult) {
+        BatteryBackupHelper.dumpHistoricalData(mContext, mPrintWriter);
+        assertThat(mStringWriter.toString().contains(expectedResult)).isTrue();
+    }
+
     private void verifyBackupData(String expectedResult) throws Exception {
         final byte[] expectedBytes = expectedResult.getBytes();
         final ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java
index 74f62ad..cb5de7d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoricalLogUtilTest.java
@@ -49,7 +49,7 @@
     @Test
     public void printHistoricalLog_withDefaultLogs() {
         BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
-        assertThat(mTestStringWriter.toString()).contains("No past logs");
+        assertThat(mTestStringWriter.toString()).contains("nothing to dump");
     }
 
     @Test
@@ -58,7 +58,7 @@
         BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);
 
         assertThat(mTestStringWriter.toString()).contains(
-                "pkg1\tAction:APPLY\tEvent:logs\tTimestamp:");
+                "pkg1\taction:APPLY\tevent:logs");
     }
 
     @Test