• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 
17 package com.android.settings.fuelgauge;
18 
19 import static com.android.settings.fuelgauge.BatteryBackupHelper.DELIMITER;
20 import static com.android.settings.fuelgauge.BatteryBackupHelper.DELIMITER_MODE;
21 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED;
22 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.ArgumentMatchers.anyLong;
29 import static org.mockito.ArgumentMatchers.anyString;
30 import static org.mockito.ArgumentMatchers.eq;
31 import static org.mockito.Mockito.atLeastOnce;
32 import static org.mockito.Mockito.doReturn;
33 import static org.mockito.Mockito.doThrow;
34 import static org.mockito.Mockito.inOrder;
35 import static org.mockito.Mockito.never;
36 import static org.mockito.Mockito.spy;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.verifyNoInteractions;
39 
40 import android.app.AppOpsManager;
41 import android.app.backup.BackupDataInputStream;
42 import android.app.backup.BackupDataOutput;
43 import android.content.Context;
44 import android.content.pm.ApplicationInfo;
45 import android.content.pm.IPackageManager;
46 import android.content.pm.PackageManager;
47 import android.content.pm.ParceledListSlice;
48 import android.content.pm.UserInfo;
49 import android.os.Build;
50 import android.os.IDeviceIdleController;
51 import android.os.RemoteException;
52 import android.os.UserHandle;
53 import android.os.UserManager;
54 import android.util.ArraySet;
55 
56 import com.android.settings.TestUtils;
57 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
58 import com.android.settings.testutils.FakeFeatureFactory;
59 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
60 
61 import org.junit.After;
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.mockito.ArgumentCaptor;
66 import org.mockito.InOrder;
67 import org.mockito.Mock;
68 import org.mockito.MockitoAnnotations;
69 import org.robolectric.RobolectricTestRunner;
70 import org.robolectric.RuntimeEnvironment;
71 import org.robolectric.annotation.Config;
72 import org.robolectric.annotation.Implementation;
73 import org.robolectric.annotation.Implements;
74 import org.robolectric.annotation.Resetter;
75 
76 import java.io.PrintWriter;
77 import java.io.StringWriter;
78 import java.util.Arrays;
79 import java.util.List;
80 import java.util.Set;
81 import java.util.concurrent.TimeUnit;
82 
83 @RunWith(RobolectricTestRunner.class)
84 @Config(shadows = {BatteryBackupHelperTest.ShadowUserHandle.class})
85 public final class BatteryBackupHelperTest {
86     private static final String PACKAGE_NAME1 = "com.android.testing.1";
87     private static final String PACKAGE_NAME2 = "com.android.testing.2";
88     private static final String PACKAGE_NAME3 = "com.android.testing.3";
89     private static final int UID1 = 1;
90 
91     private Context mContext;
92     private PrintWriter mPrintWriter;
93     private StringWriter mStringWriter;
94     private BatteryBackupHelper mBatteryBackupHelper;
95     private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
96 
97     @Mock
98     private PackageManager mPackageManager;
99     @Mock
100     private BackupDataOutput mBackupDataOutput;
101     @Mock
102     private BackupDataInputStream mBackupDataInputStream;
103     @Mock
104     private IDeviceIdleController mDeviceController;
105     @Mock
106     private IPackageManager mIPackageManager;
107     @Mock
108     private AppOpsManager mAppOpsManager;
109     @Mock
110     private UserManager mUserManager;
111     @Mock
112     private PowerAllowlistBackend mPowerAllowlistBackend;
113     @Mock
114     private BatteryOptimizeUtils mBatteryOptimizeUtils;
115 
116     @Before
setUp()117     public void setUp() throws Exception {
118         MockitoAnnotations.initMocks(this);
119         mPowerUsageFeatureProvider =
120                 FakeFeatureFactory.setupForTest().powerUsageFeatureProvider;
121         mContext = spy(RuntimeEnvironment.application);
122         mStringWriter = new StringWriter();
123         mPrintWriter = new PrintWriter(mStringWriter);
124         doReturn(mContext).when(mContext).getApplicationContext();
125         doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
126         doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
127         doReturn(mPackageManager).when(mContext).getPackageManager();
128         mBatteryBackupHelper = new BatteryBackupHelper(mContext);
129         mBatteryBackupHelper.mIDeviceIdleController = mDeviceController;
130         mBatteryBackupHelper.mIPackageManager = mIPackageManager;
131         mBatteryBackupHelper.mPowerAllowlistBackend = mPowerAllowlistBackend;
132         mBatteryBackupHelper.mBatteryOptimizeUtils = mBatteryOptimizeUtils;
133         mockUid(1001 /*fake uid*/, PACKAGE_NAME1);
134         mockUid(1002 /*fake uid*/, PACKAGE_NAME2);
135         mockUid(BatteryUtils.UID_NULL, PACKAGE_NAME3);
136     }
137 
138     @After
resetShadows()139     public void resetShadows() {
140         ShadowUserHandle.reset();
141         BatteryBackupHelper.getSharedPreferences(mContext).edit().clear().apply();
142     }
143 
144     @Test
performBackup_emptyPowerList_backupPowerList()145     public void performBackup_emptyPowerList_backupPowerList() throws Exception {
146         doReturn(new String[0]).when(mDeviceController).getFullPowerWhitelist();
147         mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null);
148 
149         verify(mBackupDataOutput, atLeastOnce()).writeEntityHeader(anyString(), anyInt());
150     }
151 
152     @Test
performBackup_remoteException_notBackupPowerList()153     public void performBackup_remoteException_notBackupPowerList() throws Exception {
154         doThrow(new RemoteException()).when(mDeviceController).getFullPowerWhitelist();
155         mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null);
156 
157         verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt());
158     }
159 
160     @Test
performBackup_nonOwner_ignoreAllBackupAction()161     public void performBackup_nonOwner_ignoreAllBackupAction() throws Exception {
162         ShadowUserHandle.setUid(1);
163         final String[] fullPowerList = {"com.android.package"};
164         doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist();
165 
166         mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null);
167 
168         verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt());
169     }
170 
171     @Test
backupOptimizationMode_nullInstalledApps_ignoreBackupOptimization()172     public void backupOptimizationMode_nullInstalledApps_ignoreBackupOptimization()
173             throws Exception {
174         final UserInfo userInfo =
175                 new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0);
176         doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt());
177         doThrow(new RuntimeException())
178                 .when(mIPackageManager)
179                 .getInstalledApplications(anyLong(), anyInt());
180 
181         mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, null);
182 
183         verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt());
184     }
185 
186     @Test
backupOptimizationMode_backupOptimizationMode()187     public void backupOptimizationMode_backupOptimizationMode() throws Exception {
188         final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1);
189         createTestingData(PACKAGE_NAME1, UID1, PACKAGE_NAME2, PACKAGE_NAME3);
190 
191         mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps);
192 
193         // 2 for UNRESTRICTED mode and 1 for RESTRICTED mode.
194         final String expectedResult = PACKAGE_NAME1 + ":2," + PACKAGE_NAME2 + ":1,";
195         verifyBackupData(expectedResult);
196         verifyDumpHistoryData("com.android.testing.1\taction:BACKUP\tevent:mode: 2");
197         verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1");
198     }
199 
200     @Test
backupOptimizationMode_backupOptimizationModeAndIgnoreSystemApp()201     public void backupOptimizationMode_backupOptimizationModeAndIgnoreSystemApp()
202             throws Exception {
203         final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1);
204         createTestingData(PACKAGE_NAME1, UID1, PACKAGE_NAME2, PACKAGE_NAME3);
205         // Sets "com.android.testing.1" as system app.
206         doReturn(true).when(mPowerAllowlistBackend).isSysAllowlisted(PACKAGE_NAME1);
207         doReturn(false).when(mPowerAllowlistBackend).isDefaultActiveApp(anyString(), anyInt());
208 
209         mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps);
210 
211         // "com.android.testing.2" for RESTRICTED mode.
212         final String expectedResult = PACKAGE_NAME2 + ":1,";
213         verifyBackupData(expectedResult);
214         verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1");
215     }
216 
217     @Test
backupOptimizationMode_backupOptimizationModeAndIgnoreDefaultApp()218     public void backupOptimizationMode_backupOptimizationModeAndIgnoreDefaultApp()
219             throws Exception {
220         final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1);
221         createTestingData(PACKAGE_NAME1, UID1, PACKAGE_NAME2, PACKAGE_NAME3);
222         // Sets "com.android.testing.1" as device default app.
223         doReturn(true).when(mPowerAllowlistBackend).isDefaultActiveApp(PACKAGE_NAME1, UID1);
224         doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(anyString());
225 
226         mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps);
227 
228         // "com.android.testing.2" for RESTRICTED mode.
229         final String expectedResult = PACKAGE_NAME2 + ":1,";
230         verifyBackupData(expectedResult);
231         verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1");
232     }
233 
234     @Test
restoreEntity_nonOwner_notReadBackupData()235     public void restoreEntity_nonOwner_notReadBackupData() throws Exception {
236         ShadowUserHandle.setUid(1);
237         mockBackupData(30 /*dataSize*/, BatteryBackupHelper.KEY_OPTIMIZATION_LIST);
238 
239         mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);
240 
241         verifyNoInteractions(mBackupDataInputStream);
242     }
243 
244     @Test
restoreEntity_zeroDataSize_notReadBackupData()245     public void restoreEntity_zeroDataSize_notReadBackupData() throws Exception {
246         final int zeroDataSize = 0;
247         mockBackupData(zeroDataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST);
248 
249         mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);
250 
251         verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt());
252     }
253 
254     @Test
restoreEntity_incorrectDataKey_notReadBackupData()255     public void restoreEntity_incorrectDataKey_notReadBackupData() throws Exception {
256         final String incorrectDataKey = "incorrect_data_key";
257         mockBackupData(30 /*dataSize*/, incorrectDataKey);
258 
259         mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);
260 
261         verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt());
262     }
263 
264     @Test
restoreEntity_readExpectedDataFromBackupData()265     public void restoreEntity_readExpectedDataFromBackupData() throws Exception {
266         final int dataSize = 30;
267         mockBackupData(dataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST);
268 
269         mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);
270 
271         final ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
272         verify(mBackupDataInputStream).read(captor.capture(), eq(0), eq(dataSize));
273         assertThat(captor.getValue().length).isEqualTo(dataSize);
274     }
275 
276     @Test
restoreEntity_verifyConfiguration()277     public void restoreEntity_verifyConfiguration() {
278         final int invalidScheduledLevel = 5;
279         TestUtils.setScheduledLevel(mContext, invalidScheduledLevel);
280 
281         mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);
282 
283         assertThat(TestUtils.getScheduledLevel(mContext)).isNotEqualTo(invalidScheduledLevel);
284     }
285 
286     @Test
restoreEntity_verifyConfigurationOneTimeOnly()287     public void restoreEntity_verifyConfigurationOneTimeOnly() {
288         final int invalidScheduledLevel = 5;
289         TestUtils.setScheduledLevel(mContext, invalidScheduledLevel);
290         mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);
291         TestUtils.setScheduledLevel(mContext, invalidScheduledLevel);
292 
293         // Invoke the restoreEntity() method 2nd time.
294         mBatteryBackupHelper.restoreEntity(mBackupDataInputStream);
295 
296         assertThat(TestUtils.getScheduledLevel(mContext))
297                 .isEqualTo(invalidScheduledLevel);
298     }
299 
300     @Test
restoreOptimizationMode_nullBytesData_skipRestore()301     public void restoreOptimizationMode_nullBytesData_skipRestore() throws Exception {
302         mBatteryBackupHelper.restoreOptimizationMode(new byte[0]);
303         verifyNoInteractions(mBatteryOptimizeUtils);
304 
305         mBatteryBackupHelper.restoreOptimizationMode("invalid data format".getBytes());
306         verifyNoInteractions(mBatteryOptimizeUtils);
307 
308         mBatteryBackupHelper.restoreOptimizationMode(DELIMITER.getBytes());
309         verifyNoInteractions(mBatteryOptimizeUtils);
310     }
311 
312     @Test
restoreOptimizationMode_invalidModeFormat_skipRestore()313     public void restoreOptimizationMode_invalidModeFormat_skipRestore() throws Exception {
314         final String invalidNumberFormat = "google";
315         final String packageModes =
316                 PACKAGE_NAME1 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER +
317                 PACKAGE_NAME2 + DELIMITER_MODE + invalidNumberFormat;
318 
319         mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes());
320         TimeUnit.SECONDS.sleep(1);
321 
322         final InOrder inOrder = inOrder(mBatteryOptimizeUtils);
323         inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_RESTRICTED, Action.RESTORE);
324         inOrder.verify(mBatteryOptimizeUtils, never())
325                 .setAppUsageState(anyInt(), eq(Action.RESTORE));
326     }
327 
328     @Test
restoreOptimizationMode_restoreExpectedModes()329     public void restoreOptimizationMode_restoreExpectedModes() throws Exception {
330         final String packageModes =
331                 PACKAGE_NAME1 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER +
332                 PACKAGE_NAME2 + DELIMITER_MODE + MODE_UNRESTRICTED + DELIMITER +
333                 PACKAGE_NAME3 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER;
334 
335         mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes());
336         TimeUnit.SECONDS.sleep(1);
337 
338         final InOrder inOrder = inOrder(mBatteryOptimizeUtils);
339         inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_RESTRICTED, Action.RESTORE);
340         inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_UNRESTRICTED, Action.RESTORE);
341         inOrder.verify(mBatteryOptimizeUtils, never())
342                 .setAppUsageState(MODE_RESTRICTED, Action.RESTORE);
343     }
344 
345     @Test
performBackup_backupDeviceBuildInformation()346     public void performBackup_backupDeviceBuildInformation() throws Exception {
347         final String[] fullPowerList = {"com.android.package"};
348         doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist();
349         doReturn(null).when(mPowerUsageFeatureProvider).getBuildMetadata1(mContext);
350         final String deviceMetadata = "device.metadata.test_device";
351         doReturn(deviceMetadata).when(mPowerUsageFeatureProvider).getBuildMetadata2(mContext);
352 
353         mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null);
354 
355         final InOrder inOrder = inOrder(mBackupDataOutput);
356         verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_BRAND, Build.BRAND);
357         verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_PRODUCT, Build.PRODUCT);
358         verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_MANUFACTURER, Build.MANUFACTURER);
359         verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_FINGERPRINT, Build.FINGERPRINT);
360         inOrder.verify(mBackupDataOutput, never()).writeEntityHeader(
361                 eq(BatteryBackupHelper.KEY_BUILD_METADATA_1), anyInt());
362         verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_METADATA_2, deviceMetadata);
363     }
364 
mockUid(int uid, String packageName)365     private void mockUid(int uid, String packageName) throws Exception {
366         doReturn(uid).when(mPackageManager)
367                 .getPackageUid(packageName, PackageManager.GET_META_DATA);
368     }
369 
mockBackupData(int dataSize, String dataKey)370     private void mockBackupData(int dataSize, String dataKey) {
371         doReturn(dataSize).when(mBackupDataInputStream).size();
372         doReturn(dataKey).when(mBackupDataInputStream).getKey();
373     }
374 
verifyDumpHistoryData(String expectedResult)375     private void verifyDumpHistoryData(String expectedResult) {
376         BatteryBackupHelper.dumpHistoricalData(mContext, mPrintWriter);
377         assertThat(mStringWriter.toString().contains(expectedResult)).isTrue();
378     }
379 
verifyBackupData(String expectedResult)380     private void verifyBackupData(String expectedResult) throws Exception {
381         final byte[] expectedBytes = expectedResult.getBytes();
382         final ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
383         final Set<String> expectedResultSet =
384                 Set.of(expectedResult.split(BatteryBackupHelper.DELIMITER));
385 
386         verify(mBackupDataOutput).writeEntityHeader(
387                 BatteryBackupHelper.KEY_OPTIMIZATION_LIST, expectedBytes.length);
388         verify(mBackupDataOutput).writeEntityData(captor.capture(), eq(expectedBytes.length));
389         final String actualResult = new String(captor.getValue());
390         final Set<String> actualResultSet =
391                 Set.of(actualResult.split(BatteryBackupHelper.DELIMITER));
392         assertThat(actualResultSet).isEqualTo(expectedResultSet);
393     }
394 
createTestingData(String packageName1, int uid1, String packageName2, String packageName3)395     private void createTestingData(String packageName1, int uid1, String packageName2,
396             String packageName3) throws Exception {
397         // Sets the getInstalledApplications() method for testing.
398         final UserInfo userInfo =
399                 new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0);
400         doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt());
401         final ApplicationInfo applicationInfo1 = new ApplicationInfo();
402         applicationInfo1.enabled = true;
403         applicationInfo1.uid = uid1;
404         applicationInfo1.packageName = packageName1;
405         final ApplicationInfo applicationInfo2 = new ApplicationInfo();
406         applicationInfo2.enabled = false;
407         applicationInfo2.uid = 2;
408         applicationInfo2.packageName = packageName2;
409         applicationInfo2.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
410         final ApplicationInfo applicationInfo3 = new ApplicationInfo();
411         applicationInfo3.enabled = false;
412         applicationInfo3.uid = 3;
413         applicationInfo3.packageName = packageName3;
414         applicationInfo3.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
415         doReturn(new ParceledListSlice<ApplicationInfo>(
416                 Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3)))
417             .when(mIPackageManager)
418             .getInstalledApplications(anyLong(), anyInt());
419         // Sets the AppOpsManager for checkOpNoThrow() method.
420         doReturn(AppOpsManager.MODE_ALLOWED)
421                 .when(mAppOpsManager)
422                 .checkOpNoThrow(
423                         AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
424                         applicationInfo1.uid,
425                         applicationInfo1.packageName);
426         doReturn(AppOpsManager.MODE_IGNORED)
427                 .when(mAppOpsManager)
428                 .checkOpNoThrow(
429                         AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
430                         applicationInfo2.uid,
431                         applicationInfo2.packageName);
432         mBatteryBackupHelper.mTestApplicationInfoList =
433                 new ArraySet<>(Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3));
434     }
435 
verifyBackupData( InOrder inOrder, String dataKey, String dataContent)436     private void verifyBackupData(
437             InOrder inOrder, String dataKey, String dataContent) throws Exception {
438         final byte[] expectedBytes = dataContent.getBytes();
439         inOrder.verify(mBackupDataOutput).writeEntityHeader(dataKey, expectedBytes.length);
440         inOrder.verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length);
441     }
442 
443     @Implements(UserHandle.class)
444     public static class ShadowUserHandle {
445         // Sets the default as thte OWNER role.
446         private static int sUid = 0;
447 
setUid(int uid)448         public static void setUid(int uid) {
449             sUid = uid;
450         }
451 
452         @Implementation
myUserId()453         public static int myUserId() {
454             return sUid;
455         }
456 
457         @Resetter
reset()458         public static void reset() {
459             sUid = 0;
460         }
461     }
462 }
463