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

Commit ea57d686 authored by ykhung's avatar ykhung Committed by Automerger Merge Worker
Browse files

[B&R] restore the backup data for app optimization mode back am: adec23a1

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/15681503

Change-Id: I4977c0f78803d943426a7b2d64b0304bbc2c0b8a
parents b089d5fd adec23a1
Loading
Loading
Loading
Loading
+82 −7
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.IDeviceIdleController;
import android.os.RemoteException;
import android.os.ParcelFileDescriptor;
@@ -40,6 +41,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settingslib.fuelgauge.PowerAllowlistBackend;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -49,7 +51,7 @@ 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 boolean DEBUG = false;
    private static final boolean DEBUG = Build.TYPE.equals("userdebug");

    // Only the owner can see all apps.
    private static final int RETRIEVE_FLAG_ADMIN =
@@ -60,8 +62,8 @@ public final class BatteryBackupHelper implements BackupHelper {
            PackageManager.MATCH_DISABLED_COMPONENTS |
            PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;

    static final CharSequence DELIMITER = ",";
    static final CharSequence DELIMITER_MODE = "|";
    static final String DELIMITER = ",";
    static final String DELIMITER_MODE = ":";
    static final String KEY_FULL_POWER_LIST = "full_power_list";
    static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list";

@@ -71,6 +73,8 @@ public final class BatteryBackupHelper implements BackupHelper {
    IDeviceIdleController mIDeviceIdleController;
    @VisibleForTesting
    IPackageManager mIPackageManager;
    @VisibleForTesting
    BatteryOptimizeUtils mBatteryOptimizeUtils;

    private final Context mContext;

@@ -81,8 +85,8 @@ public final class BatteryBackupHelper implements BackupHelper {
    @Override
    public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
            ParcelFileDescriptor newState) {
        if (!isOwner()) {
            Log.w(TAG, "ignore performBackup() for non-owner");
        if (!isOwner() || data == null) {
            Log.w(TAG, "ignore performBackup() for non-owner or empty data");
            return;
        }
        final List<String> allowlistedApps = backupFullPowerList(data);
@@ -93,7 +97,21 @@ public final class BatteryBackupHelper implements BackupHelper {

    @Override
    public void restoreEntity(BackupDataInputStream data) {
        Log.d(TAG, "restoreEntity()");
        if (!isOwner() || data == null || data.size() == 0) {
            Log.w(TAG, "ignore restoreEntity() for non-owner or empty data");
            return;
        }
        if (KEY_OPTIMIZATION_LIST.equals(data.getKey())) {
            final int dataSize = data.size();
            final byte[] dataBytes = new byte[dataSize];
            try {
                data.read(dataBytes, 0 /*offset*/, dataSize);
            } catch (IOException e) {
                Log.e(TAG, "failed to load BackupDataInputStream", e);
                return;
            }
            restoreOptimizationMode(dataBytes);
        }
    }

    @Override
@@ -115,7 +133,6 @@ public final class BatteryBackupHelper implements BackupHelper {
            return new ArrayList<>();
        }

        debugLog("allowlistedApps:" + Arrays.toString(allowlistedApps));
        final String allowedApps = String.join(DELIMITER, allowlistedApps);
        writeBackupData(data, KEY_FULL_POWER_LIST, allowedApps);
        Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms",
@@ -159,6 +176,64 @@ public final class BatteryBackupHelper implements BackupHelper {
                applications.size(), backupCount, (System.currentTimeMillis() - timestamp)));
    }

    @VisibleForTesting
    void restoreOptimizationMode(byte[] dataBytes) {
        final long timestamp = System.currentTimeMillis();
        final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
        if (dataContent == null || dataContent.isEmpty()) {
            Log.w(TAG, "no data found in the restoreOptimizationMode()");
            return;
        }
        final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
        if (appConfigurations == null || appConfigurations.length == 0) {
            Log.w(TAG, "no data found from the split() processing");
            return;
        }
        int restoreCount = 0;
        for (int index = 0; index < appConfigurations.length; index++) {
            final String[] results = appConfigurations[index]
                    .split(BatteryBackupHelper.DELIMITER_MODE);
            // Example format: com.android.systemui:2 we should have length=2
            if (results == null || results.length != 2) {
                Log.w(TAG, "invalid raw data found:" + appConfigurations[index]);
                continue;
            }
            final String packageName = results[0];
            // Ignores system/default apps.
            if (isSystemOrDefaultApp(packageName)) {
                Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName);
                continue;
            }
            @BatteryOptimizeUtils.OptimizationMode
            int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
            try {
                optimizationMode = Integer.parseInt(results[1]);
            } catch (NumberFormatException e) {
                Log.e(TAG, "failed to parse the optimization mode: "
                        + appConfigurations[index], e);
                continue;
            }
            restoreOptimizationMode(packageName, optimizationMode);
            restoreCount++;
        }
        Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms",
                restoreCount, (System.currentTimeMillis() - timestamp)));
    }

    private void restoreOptimizationMode(
            String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
        final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
        if (uid == BatteryUtils.UID_NULL) {
            return;
        }
        final BatteryOptimizeUtils batteryOptimizeUtils =
                mBatteryOptimizeUtils != null
                        ? mBatteryOptimizeUtils /*testing only*/
                        : new BatteryOptimizeUtils(mContext, uid, packageName);
        batteryOptimizeUtils.setAppOptimizationMode(mode);
        Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode));
    }

    // Provides an opportunity to inject mock IDeviceIdleController for testing.
    private IDeviceIdleController getIDeviceIdleController() {
        if (mIDeviceIdleController != null) {
+120 −6
Original line number Diff line number Diff line
@@ -16,18 +16,27 @@

package com.android.settings.fuelgauge;

import static com.android.settings.fuelgauge.BatteryBackupHelper.DELIMITER;
import static com.android.settings.fuelgauge.BatteryBackupHelper.DELIMITER_MODE;
import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED;
import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import android.app.AppOpsManager;
import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -49,6 +58,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -68,9 +79,13 @@ public final class BatteryBackupHelperTest {
    private Context mContext;
    private BatteryBackupHelper mBatteryBackupHelper;

    @Mock
    private PackageManager mPackageManager;
    @Mock
    private BackupDataOutput mBackupDataOutput;
    @Mock
    private BackupDataInputStream mBackupDataInputStream;
    @Mock
    private IDeviceIdleController mDeviceController;
    @Mock
    private IPackageManager mIPackageManager;
@@ -80,18 +95,25 @@ public final class BatteryBackupHelperTest {
    private UserManager mUserManager;
    @Mock
    private PowerAllowlistBackend mPowerAllowlistBackend;
    @Mock
    private BatteryOptimizeUtils mBatteryOptimizeUtils;

    @Before
    public void setUp() {
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mContext = spy(RuntimeEnvironment.application);
        doReturn(mContext).when(mContext).getApplicationContext();
        doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
        doReturn(mPackageManager).when(mContext).getPackageManager();
        mBatteryBackupHelper = new BatteryBackupHelper(mContext);
        mBatteryBackupHelper.mIDeviceIdleController = mDeviceController;
        mBatteryBackupHelper.mIPackageManager = mIPackageManager;
        mBatteryBackupHelper.mPowerAllowlistBackend = mPowerAllowlistBackend;
        mBatteryBackupHelper.mBatteryOptimizeUtils = mBatteryOptimizeUtils;
        mockUid(1001 /*fake uid*/, PACKAGE_NAME1);
        mockUid(1002 /*fake uid*/, PACKAGE_NAME2);
        mockUid(BatteryUtils.UID_NULL, PACKAGE_NAME3);
    }

    @After
@@ -144,8 +166,7 @@ public final class BatteryBackupHelperTest {

        mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null);

        final String expectedResult = fullPowerList[0]
                + BatteryBackupHelper.DELIMITER + fullPowerList[1];
        final String expectedResult = fullPowerList[0] + DELIMITER + fullPowerList[1];
        final byte[] expectedBytes = expectedResult.getBytes();
        verify(mBackupDataOutput).writeEntityHeader(
                BatteryBackupHelper.KEY_FULL_POWER_LIST, expectedBytes.length);
@@ -186,7 +207,7 @@ public final class BatteryBackupHelperTest {
        mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps);

        // 2 for UNRESTRICTED mode and 1 for RESTRICTED mode.
        final String expectedResult = PACKAGE_NAME1 + "|2," + PACKAGE_NAME2 + "|1,";
        final String expectedResult = PACKAGE_NAME1 + ":2," + PACKAGE_NAME2 + ":1,";
        verifyBackupData(expectedResult);
    }

@@ -202,7 +223,7 @@ public final class BatteryBackupHelperTest {
        mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps);

        // "com.android.testing.2" for RESTRICTED mode.
        final String expectedResult = PACKAGE_NAME2 + "|1,";
        final String expectedResult = PACKAGE_NAME2 + ":1,";
        verifyBackupData(expectedResult);
    }

@@ -218,10 +239,103 @@ public final class BatteryBackupHelperTest {
        mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps);

        // "com.android.testing.2" for RESTRICTED mode.
        final String expectedResult = PACKAGE_NAME2 + "|1,";
        final String expectedResult = PACKAGE_NAME2 + ":1,";
        verifyBackupData(expectedResult);
    }

    @Test
    public void restoreEntity_nonOwner_notReadBackupData() throws Exception {
        ShadowUserHandle.setUid(1);
        mockBackupData(30 /*dataSize*/, BatteryBackupHelper.KEY_OPTIMIZATION_LIST);

        mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);

        verifyZeroInteractions(mBackupDataInputStream);
    }

    @Test
    public void restoreEntity_zeroDataSize_notReadBackupData() throws Exception {
        final int zeroDataSize = 0;
        mockBackupData(zeroDataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST);

        mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);

        verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt());
    }

    @Test
    public void restoreEntity_incorrectDataKey_notReadBackupData() throws Exception {
        final String incorrectDataKey = BatteryBackupHelper.KEY_FULL_POWER_LIST;
        mockBackupData(30 /*dataSize*/, incorrectDataKey);

        mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);

        verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt());
    }

    @Test
    public void restoreEntity_readExpectedDataFromBackupData() throws Exception {
        final int dataSize = 30;
        mockBackupData(dataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST);

        mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);

        final ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
        verify(mBackupDataInputStream).read(captor.capture(), eq(0), eq(dataSize));
        assertThat(captor.getValue().length).isEqualTo(dataSize);
    }

    @Test
    public void restoreOptimizationMode_nullBytesData_skipRestore() throws Exception {
        mBatteryBackupHelper.restoreOptimizationMode(new byte[0]);
        verifyZeroInteractions(mBatteryOptimizeUtils);

        mBatteryBackupHelper.restoreOptimizationMode("invalid data format".getBytes());
        verifyZeroInteractions(mBatteryOptimizeUtils);

        mBatteryBackupHelper.restoreOptimizationMode(DELIMITER.getBytes());
        verifyZeroInteractions(mBatteryOptimizeUtils);
    }

    @Test
    public void restoreOptimizationMode_invalidModeFormat_skipRestore() throws Exception {
        final String invalidNumberFormat = "google";
        final String packageModes =
                PACKAGE_NAME1 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER +
                PACKAGE_NAME2 + DELIMITER_MODE + invalidNumberFormat;

        mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes());

        final InOrder inOrder = inOrder(mBatteryOptimizeUtils);
        inOrder.verify(mBatteryOptimizeUtils).setAppOptimizationMode(MODE_RESTRICTED);
        inOrder.verify(mBatteryOptimizeUtils, never()).setAppOptimizationMode(anyInt());
    }

    @Test
    public void restoreOptimizationMode_restoreExpectedModes() throws Exception {
        final String packageModes =
                PACKAGE_NAME1 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER +
                PACKAGE_NAME2 + DELIMITER_MODE + MODE_UNRESTRICTED + DELIMITER +
                PACKAGE_NAME3 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER;

        mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes());

        final InOrder inOrder = inOrder(mBatteryOptimizeUtils);
        inOrder.verify(mBatteryOptimizeUtils).setAppOptimizationMode(MODE_RESTRICTED);
        inOrder.verify(mBatteryOptimizeUtils).setAppOptimizationMode(MODE_UNRESTRICTED);
        inOrder.verify(mBatteryOptimizeUtils, never()).setAppOptimizationMode(MODE_RESTRICTED);
    }

    private void mockUid(int uid, String packageName) throws Exception {
        doReturn(uid).when(mPackageManager)
                .getPackageUid(packageName, PackageManager.GET_META_DATA);
    }

    private void mockBackupData(int dataSize, String dataKey) {
        doReturn(dataSize).when(mBackupDataInputStream).size();
        doReturn(dataKey).when(mBackupDataInputStream).getKey();
    }

    private void verifyBackupData(String expectedResult) throws Exception {
        final byte[] expectedBytes = expectedResult.getBytes();
        verify(mBackupDataOutput).writeEntityHeader(