Loading src/com/android/settings/fuelgauge/BatteryBackupHelper.java +72 −2 Original line number Diff line number Diff line Loading @@ -20,24 +20,47 @@ import android.app.backup.BackupDataInputStream; import android.app.backup.BackupDataOutput; import android.app.backup.BackupHelper; import android.content.Context; import android.os.IDeviceIdleController; import android.os.RemoteException; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.io.IOException; import java.util.Arrays; /** An implementation to backup and restore battery configurations. */ 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; @VisibleForTesting static final CharSequence DELIMITER = ":"; @VisibleForTesting static final String KEY_FULL_POWER_LIST = "full_power_list"; @VisibleForTesting IDeviceIdleController mIDeviceIdleController; private final Context mContext; public BatteryBackupHelper(Context context) { mContext = context; mContext = context.getApplicationContext(); } @Override public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { Log.d(TAG, "performBackup()"); if (!isOwner()) { Log.w(TAG, "ignore the backup process for non-owner"); return; } backupFullPowerList(getIDeviceIdleController(), data); } @Override Loading @@ -48,4 +71,51 @@ public final class BatteryBackupHelper implements BackupHelper { @Override public void writeNewStateDescription(ParcelFileDescriptor newState) { } private void backupFullPowerList( IDeviceIdleController deviceIdleService, BackupDataOutput data) { final long timestamp = System.currentTimeMillis(); String[] allowlistedApps; try { allowlistedApps = deviceIdleService.getFullPowerWhitelist(); } catch (RemoteException e) { Log.e(TAG, "backupFullPowerList() failed", e); return; } // Ignores unexpected emptty result case. if (allowlistedApps == null || allowlistedApps.length == 0) { Log.w(TAG, "no data found in the getFullPowerList()"); return; } debugLog("allowlistedApps:" + Arrays.toString(allowlistedApps)); final String allowedApps = String.join(DELIMITER, allowlistedApps); final byte[] allowedAppsBytes = allowedApps.getBytes(); try { data.writeEntityHeader(KEY_FULL_POWER_LIST, allowedAppsBytes.length); data.writeEntityData(allowedAppsBytes, allowedAppsBytes.length); } catch (IOException e) { Log.e(TAG, "backup getFullPowerList() failed", e); return; } Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms", allowlistedApps.length, (System.currentTimeMillis() - timestamp))); } // Provides an opportunity to inject mock IDeviceIdleController for testing. private IDeviceIdleController getIDeviceIdleController() { if (mIDeviceIdleController != null) { return mIDeviceIdleController; } mIDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(DEVICE_IDLE_SERVICE)); return mIDeviceIdleController; } private void debugLog(String debugContent) { if (DEBUG) Log.d(TAG, debugContent); } private static boolean isOwner() { return UserHandle.myUserId() == UserHandle.USER_OWNER; } } tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.backup.BackupDataOutput; import android.content.Context; import android.os.IDeviceIdleController; import android.os.RemoteException; import android.os.UserHandle; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; @RunWith(RobolectricTestRunner.class) @Config(shadows = {BatteryBackupHelperTest.ShadowUserHandle.class}) public final class BatteryBackupHelperTest { private Context mContext; private BatteryBackupHelper mBatteryBackupHelper; @Mock private BackupDataOutput mBackupDataOutput; @Mock private IDeviceIdleController mDeviceController; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); mBatteryBackupHelper = new BatteryBackupHelper(mContext); mBatteryBackupHelper.mIDeviceIdleController = mDeviceController; } @After public void resetShadows() { ShadowUserHandle.reset(); } @Test public void performBackup_nullPowerList_notBackupPowerList() throws Exception { doReturn(null).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Test public void performBackup_emptyPowerList_notBackupPowerList() throws Exception { doReturn(new String[0]).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Test public void performBackup_remoteException_notBackupPowerList() throws Exception { doThrow(new RemoteException()).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Test public void performBackup_oneFullPowerListElement_backupFullPowerListData() throws Exception { final String[] fullPowerList = {"com.android.package"}; doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); final byte[] expectedBytes = fullPowerList[0].getBytes(); verify(mBackupDataOutput).writeEntityHeader( BatteryBackupHelper.KEY_FULL_POWER_LIST, expectedBytes.length); verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); } @Test public void performBackup_backupFullPowerListData() throws Exception { final String[] fullPowerList = {"com.android.package1", "com.android.package2"}; doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); final String expectedResult = fullPowerList[0] + BatteryBackupHelper.DELIMITER + fullPowerList[1]; final byte[] expectedBytes = expectedResult.getBytes(); verify(mBackupDataOutput).writeEntityHeader( BatteryBackupHelper.KEY_FULL_POWER_LIST, expectedBytes.length); verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); } @Test public void performBackup_nonOwner_ignoreAllBackupAction() throws Exception { ShadowUserHandle.setUid(1); final String[] fullPowerList = {"com.android.package"}; doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Implements(UserHandle.class) public static class ShadowUserHandle { // Sets the default as thte OWNER role. private static int sUid = 0; public static void setUid(int uid) { sUid = uid; } @Implementation public static int myUserId() { return sUid; } @Resetter public static void reset() { sUid = 0; } } } Loading
src/com/android/settings/fuelgauge/BatteryBackupHelper.java +72 −2 Original line number Diff line number Diff line Loading @@ -20,24 +20,47 @@ import android.app.backup.BackupDataInputStream; import android.app.backup.BackupDataOutput; import android.app.backup.BackupHelper; import android.content.Context; import android.os.IDeviceIdleController; import android.os.RemoteException; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.io.IOException; import java.util.Arrays; /** An implementation to backup and restore battery configurations. */ 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; @VisibleForTesting static final CharSequence DELIMITER = ":"; @VisibleForTesting static final String KEY_FULL_POWER_LIST = "full_power_list"; @VisibleForTesting IDeviceIdleController mIDeviceIdleController; private final Context mContext; public BatteryBackupHelper(Context context) { mContext = context; mContext = context.getApplicationContext(); } @Override public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { Log.d(TAG, "performBackup()"); if (!isOwner()) { Log.w(TAG, "ignore the backup process for non-owner"); return; } backupFullPowerList(getIDeviceIdleController(), data); } @Override Loading @@ -48,4 +71,51 @@ public final class BatteryBackupHelper implements BackupHelper { @Override public void writeNewStateDescription(ParcelFileDescriptor newState) { } private void backupFullPowerList( IDeviceIdleController deviceIdleService, BackupDataOutput data) { final long timestamp = System.currentTimeMillis(); String[] allowlistedApps; try { allowlistedApps = deviceIdleService.getFullPowerWhitelist(); } catch (RemoteException e) { Log.e(TAG, "backupFullPowerList() failed", e); return; } // Ignores unexpected emptty result case. if (allowlistedApps == null || allowlistedApps.length == 0) { Log.w(TAG, "no data found in the getFullPowerList()"); return; } debugLog("allowlistedApps:" + Arrays.toString(allowlistedApps)); final String allowedApps = String.join(DELIMITER, allowlistedApps); final byte[] allowedAppsBytes = allowedApps.getBytes(); try { data.writeEntityHeader(KEY_FULL_POWER_LIST, allowedAppsBytes.length); data.writeEntityData(allowedAppsBytes, allowedAppsBytes.length); } catch (IOException e) { Log.e(TAG, "backup getFullPowerList() failed", e); return; } Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms", allowlistedApps.length, (System.currentTimeMillis() - timestamp))); } // Provides an opportunity to inject mock IDeviceIdleController for testing. private IDeviceIdleController getIDeviceIdleController() { if (mIDeviceIdleController != null) { return mIDeviceIdleController; } mIDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(DEVICE_IDLE_SERVICE)); return mIDeviceIdleController; } private void debugLog(String debugContent) { if (DEBUG) Log.d(TAG, debugContent); } private static boolean isOwner() { return UserHandle.myUserId() == UserHandle.USER_OWNER; } }
tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java 0 → 100644 +157 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.backup.BackupDataOutput; import android.content.Context; import android.os.IDeviceIdleController; import android.os.RemoteException; import android.os.UserHandle; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; @RunWith(RobolectricTestRunner.class) @Config(shadows = {BatteryBackupHelperTest.ShadowUserHandle.class}) public final class BatteryBackupHelperTest { private Context mContext; private BatteryBackupHelper mBatteryBackupHelper; @Mock private BackupDataOutput mBackupDataOutput; @Mock private IDeviceIdleController mDeviceController; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); mBatteryBackupHelper = new BatteryBackupHelper(mContext); mBatteryBackupHelper.mIDeviceIdleController = mDeviceController; } @After public void resetShadows() { ShadowUserHandle.reset(); } @Test public void performBackup_nullPowerList_notBackupPowerList() throws Exception { doReturn(null).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Test public void performBackup_emptyPowerList_notBackupPowerList() throws Exception { doReturn(new String[0]).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Test public void performBackup_remoteException_notBackupPowerList() throws Exception { doThrow(new RemoteException()).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Test public void performBackup_oneFullPowerListElement_backupFullPowerListData() throws Exception { final String[] fullPowerList = {"com.android.package"}; doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); final byte[] expectedBytes = fullPowerList[0].getBytes(); verify(mBackupDataOutput).writeEntityHeader( BatteryBackupHelper.KEY_FULL_POWER_LIST, expectedBytes.length); verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); } @Test public void performBackup_backupFullPowerListData() throws Exception { final String[] fullPowerList = {"com.android.package1", "com.android.package2"}; doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); final String expectedResult = fullPowerList[0] + BatteryBackupHelper.DELIMITER + fullPowerList[1]; final byte[] expectedBytes = expectedResult.getBytes(); verify(mBackupDataOutput).writeEntityHeader( BatteryBackupHelper.KEY_FULL_POWER_LIST, expectedBytes.length); verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); } @Test public void performBackup_nonOwner_ignoreAllBackupAction() throws Exception { ShadowUserHandle.setUid(1); final String[] fullPowerList = {"com.android.package"}; doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); } @Implements(UserHandle.class) public static class ShadowUserHandle { // Sets the default as thte OWNER role. private static int sUid = 0; public static void setUid(int uid) { sUid = uid; } @Implementation public static int myUserId() { return sUid; } @Resetter public static void reset() { sUid = 0; } } }