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