[B&R] backup the getFullPowerWhitelist() for battery setting

BYPASS_INCLUSIVE_LANGUAGE_REASON=legacy method name

Test command for backup manager:
adb shell bmgr backupnow com.android.settings
adb shell dumpsys backup | grep Current

Bug: 192523697
Test: make SettingsRoboTests
Change-Id: I2d5c51b5b5788d703a233f5df719a298382feb98
diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
index 2b5576d..aeb5e9f 100644
--- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
+++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
@@ -20,24 +20,47 @@
 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
@@ -48,4 +71,51 @@
     @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;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
new file mode 100644
index 0000000..87aa812
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+        }
+    }
+}