Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8f561144 authored by ykhung's avatar ykhung
Browse files

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
parent 002f06e1
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ message BatteryOptimizeHistoricalLogEntry {
    APPLY = 2;
    RESET = 3;
    RESTORE = 4;
    BACKUP = 5;
  }

  optional string package_name = 1;
+21 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.app.backup.BackupDataInputStream;
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 android.util.Log;

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 @@ public final class BatteryBackupHelper implements BackupHelper {
    /** 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 @@ public final class BatteryBackupHelper implements BackupHelper {
        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 @@ public final class BatteryBackupHelper implements BackupHelper {
                    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 @@ public final class BatteryBackupHelper implements BackupHelper {
                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);
+32 −30
Original line number Diff line number Diff line
@@ -37,40 +37,40 @@ public final class BatteryHistoricalLogUtil {
    @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 @@ public final class BatteryHistoricalLogUtil {
                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
+16 −0
Original line number Diff line number Diff line
@@ -70,6 +70,8 @@ import org.robolectric.annotation.Implementation;
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 @@ public final class BatteryBackupHelperTest {
    private static final int UID1 = 1;

    private Context mContext;
    private PrintWriter mPrintWriter;
    private StringWriter mStringWriter;
    private BatteryBackupHelper mBatteryBackupHelper;

    @Mock
@@ -109,6 +113,8 @@ public final class BatteryBackupHelperTest {
    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 @@ public final class BatteryBackupHelperTest {
    @After
    public void resetShadows() {
        ShadowUserHandle.reset();
        BatteryBackupHelper.getSharedPreferences(mContext).edit().clear().apply();
    }

    @Test
@@ -216,6 +223,8 @@ public final class BatteryBackupHelperTest {
        // 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 @@ public final class BatteryBackupHelperTest {
        // "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 @@ public final class BatteryBackupHelperTest {
        // "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 @@ public final class BatteryBackupHelperTest {
        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);
+2 −2
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ public final class BatteryHistoricalLogUtilTest {
    @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 @@ public final class BatteryHistoricalLogUtilTest {
        BatteryHistoricalLogUtil.printBatteryOptimizeHistoricalLog(mContext, mTestPrintWriter);

        assertThat(mTestStringWriter.toString()).contains(
                "pkg1\tAction:APPLY\tEvent:logs\tTimestamp:");
                "pkg1\taction:APPLY\tevent:logs");
    }

    @Test