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 private PackageManager mPackageManager; 98 @Mock private BackupDataOutput mBackupDataOutput; 99 @Mock private BackupDataInputStream mBackupDataInputStream; 100 @Mock private IDeviceIdleController mDeviceController; 101 @Mock private IPackageManager mIPackageManager; 102 @Mock private AppOpsManager mAppOpsManager; 103 @Mock private UserManager mUserManager; 104 @Mock private PowerAllowlistBackend mPowerAllowlistBackend; 105 @Mock private BatteryOptimizeUtils mBatteryOptimizeUtils; 106 107 @Before setUp()108 public void setUp() throws Exception { 109 MockitoAnnotations.initMocks(this); 110 mPowerUsageFeatureProvider = FakeFeatureFactory.setupForTest().powerUsageFeatureProvider; 111 mContext = spy(RuntimeEnvironment.application); 112 mStringWriter = new StringWriter(); 113 mPrintWriter = new PrintWriter(mStringWriter); 114 BatteryUtils.getInstance(mContext).reset(); 115 doReturn(mContext).when(mContext).getApplicationContext(); 116 doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class); 117 doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); 118 doReturn(mPackageManager).when(mContext).getPackageManager(); 119 mBatteryBackupHelper = new BatteryBackupHelper(mContext); 120 mBatteryBackupHelper.mIDeviceIdleController = mDeviceController; 121 mBatteryBackupHelper.mIPackageManager = mIPackageManager; 122 mBatteryBackupHelper.mPowerAllowlistBackend = mPowerAllowlistBackend; 123 mBatteryBackupHelper.mBatteryOptimizeUtils = mBatteryOptimizeUtils; 124 mockUid(1001 /*fake uid*/, PACKAGE_NAME1); 125 mockUid(1002 /*fake uid*/, PACKAGE_NAME2); 126 mockUid(BatteryUtils.UID_NULL, PACKAGE_NAME3); 127 } 128 129 @After resetShadows()130 public void resetShadows() { 131 ShadowUserHandle.reset(); 132 BatteryBackupHelper.getSharedPreferences(mContext).edit().clear().apply(); 133 } 134 135 @Test performBackup_emptyPowerList_backupPowerList()136 public void performBackup_emptyPowerList_backupPowerList() throws Exception { 137 doReturn(new String[0]).when(mDeviceController).getFullPowerWhitelist(); 138 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 139 140 verify(mBackupDataOutput, atLeastOnce()).writeEntityHeader(anyString(), anyInt()); 141 } 142 143 @Test performBackup_remoteException_notBackupPowerList()144 public void performBackup_remoteException_notBackupPowerList() throws Exception { 145 doThrow(new RemoteException()).when(mDeviceController).getFullPowerWhitelist(); 146 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 147 148 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 149 } 150 151 @Test performBackup_nonOwner_ignoreAllBackupAction()152 public void performBackup_nonOwner_ignoreAllBackupAction() throws Exception { 153 ShadowUserHandle.setUid(1); 154 final String[] fullPowerList = {"com.android.package"}; 155 doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); 156 157 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 158 159 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 160 } 161 162 @Test backupOptimizationMode_nullInstalledApps_ignoreBackupOptimization()163 public void backupOptimizationMode_nullInstalledApps_ignoreBackupOptimization() 164 throws Exception { 165 final UserInfo userInfo = 166 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0); 167 doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt()); 168 doThrow(new RuntimeException()) 169 .when(mIPackageManager) 170 .getInstalledApplications(anyLong(), anyInt()); 171 172 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, null); 173 174 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 175 } 176 177 @Test backupOptimizationMode_backupOptimizationMode()178 public void backupOptimizationMode_backupOptimizationMode() throws Exception { 179 final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1); 180 createTestingData(PACKAGE_NAME1, UID1, PACKAGE_NAME2, PACKAGE_NAME3); 181 182 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps); 183 184 // 2 for UNRESTRICTED mode and 1 for RESTRICTED mode. 185 final String expectedResult = PACKAGE_NAME1 + ":2," + PACKAGE_NAME2 + ":1,"; 186 verifyBackupData(expectedResult); 187 verifyDumpHistoryData("com.android.testing.1\taction:BACKUP\tevent:mode: 2"); 188 verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1"); 189 } 190 191 @Test backupOptimizationMode_backupOptimizationModeAndIgnoreSystemApp()192 public void backupOptimizationMode_backupOptimizationModeAndIgnoreSystemApp() throws Exception { 193 final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1); 194 createTestingData(PACKAGE_NAME1, UID1, PACKAGE_NAME2, PACKAGE_NAME3); 195 // Sets "com.android.testing.1" as system app. 196 doReturn(true).when(mPowerAllowlistBackend).isSysAllowlisted(PACKAGE_NAME1); 197 doReturn(false).when(mPowerAllowlistBackend).isDefaultActiveApp(anyString(), anyInt()); 198 199 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps); 200 201 // "com.android.testing.2" for RESTRICTED mode. 202 final String expectedResult = PACKAGE_NAME2 + ":1,"; 203 verifyBackupData(expectedResult); 204 verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1"); 205 } 206 207 @Test backupOptimizationMode_backupOptimizationModeAndIgnoreDefaultApp()208 public void backupOptimizationMode_backupOptimizationModeAndIgnoreDefaultApp() 209 throws Exception { 210 final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1); 211 createTestingData(PACKAGE_NAME1, UID1, PACKAGE_NAME2, PACKAGE_NAME3); 212 // Sets "com.android.testing.1" as device default app. 213 doReturn(true).when(mPowerAllowlistBackend).isDefaultActiveApp(PACKAGE_NAME1, UID1); 214 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(anyString()); 215 216 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps); 217 218 // "com.android.testing.2" for RESTRICTED mode. 219 final String expectedResult = PACKAGE_NAME2 + ":1,"; 220 verifyBackupData(expectedResult); 221 verifyDumpHistoryData("com.android.testing.2\taction:BACKUP\tevent:mode: 1"); 222 } 223 224 @Test restoreEntity_nonOwner_notReadBackupData()225 public void restoreEntity_nonOwner_notReadBackupData() throws Exception { 226 ShadowUserHandle.setUid(1); 227 mockBackupData(30 /*dataSize*/, BatteryBackupHelper.KEY_OPTIMIZATION_LIST); 228 229 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 230 231 verifyNoInteractions(mBackupDataInputStream); 232 } 233 234 @Test restoreEntity_zeroDataSize_notReadBackupData()235 public void restoreEntity_zeroDataSize_notReadBackupData() throws Exception { 236 final int zeroDataSize = 0; 237 mockBackupData(zeroDataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST); 238 239 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 240 241 verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt()); 242 } 243 244 @Test restoreEntity_incorrectDataKey_notReadBackupData()245 public void restoreEntity_incorrectDataKey_notReadBackupData() throws Exception { 246 final String incorrectDataKey = "incorrect_data_key"; 247 mockBackupData(30 /*dataSize*/, incorrectDataKey); 248 249 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 250 251 verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt()); 252 } 253 254 @Test restoreEntity_readExpectedDataFromBackupData()255 public void restoreEntity_readExpectedDataFromBackupData() throws Exception { 256 final int dataSize = 30; 257 mockBackupData(dataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST); 258 259 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 260 261 final ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class); 262 verify(mBackupDataInputStream).read(captor.capture(), eq(0), eq(dataSize)); 263 assertThat(captor.getValue().length).isEqualTo(dataSize); 264 } 265 266 @Test restoreEntity_verifyConfiguration()267 public void restoreEntity_verifyConfiguration() { 268 final int invalidScheduledLevel = 5; 269 TestUtils.setScheduledLevel(mContext, invalidScheduledLevel); 270 271 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 272 273 assertThat(TestUtils.getScheduledLevel(mContext)).isNotEqualTo(invalidScheduledLevel); 274 } 275 276 @Test restoreEntity_verifyConfigurationOneTimeOnly()277 public void restoreEntity_verifyConfigurationOneTimeOnly() { 278 final int invalidScheduledLevel = 5; 279 TestUtils.setScheduledLevel(mContext, invalidScheduledLevel); 280 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 281 TestUtils.setScheduledLevel(mContext, invalidScheduledLevel); 282 283 // Invoke the restoreEntity() method 2nd time. 284 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 285 286 assertThat(TestUtils.getScheduledLevel(mContext)).isEqualTo(invalidScheduledLevel); 287 } 288 289 @Test restoreOptimizationMode_nullBytesData_skipRestore()290 public void restoreOptimizationMode_nullBytesData_skipRestore() throws Exception { 291 mBatteryBackupHelper.restoreOptimizationMode(new byte[0]); 292 verifyNoInteractions(mBatteryOptimizeUtils); 293 294 mBatteryBackupHelper.restoreOptimizationMode("invalid data format".getBytes()); 295 verifyNoInteractions(mBatteryOptimizeUtils); 296 297 mBatteryBackupHelper.restoreOptimizationMode(DELIMITER.getBytes()); 298 verifyNoInteractions(mBatteryOptimizeUtils); 299 } 300 301 @Test restoreOptimizationMode_invalidModeFormat_skipRestore()302 public void restoreOptimizationMode_invalidModeFormat_skipRestore() throws Exception { 303 final String invalidNumberFormat = "google"; 304 final String package1Mode = PACKAGE_NAME1 305 + DELIMITER_MODE 306 + MODE_RESTRICTED 307 + DELIMITER; 308 final String package2Mode = PACKAGE_NAME2 309 + DELIMITER_MODE 310 + invalidNumberFormat; 311 final String packageModes = package1Mode + package2Mode; 312 313 mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes()); 314 TimeUnit.SECONDS.sleep(1); 315 316 final InOrder inOrder = inOrder(mBatteryOptimizeUtils); 317 inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_RESTRICTED, Action.RESTORE); 318 inOrder.verify(mBatteryOptimizeUtils, never()) 319 .setAppUsageState(anyInt(), eq(Action.RESTORE)); 320 } 321 322 @Test restoreOptimizationMode_restoreExpectedModes()323 public void restoreOptimizationMode_restoreExpectedModes() throws Exception { 324 final String package1Mode = PACKAGE_NAME1 325 + DELIMITER_MODE 326 + MODE_RESTRICTED 327 + DELIMITER; 328 final String package2Mode = PACKAGE_NAME2 329 + DELIMITER_MODE 330 + MODE_UNRESTRICTED 331 + DELIMITER; 332 final String package3Mode = PACKAGE_NAME3 333 + DELIMITER_MODE 334 + MODE_RESTRICTED 335 + DELIMITER; 336 final String packageModes = package1Mode + package2Mode + package3Mode; 337 338 mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes()); 339 TimeUnit.SECONDS.sleep(1); 340 341 final InOrder inOrder = inOrder(mBatteryOptimizeUtils); 342 inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_RESTRICTED, Action.RESTORE); 343 inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_UNRESTRICTED, Action.RESTORE); 344 inOrder.verify(mBatteryOptimizeUtils, never()) 345 .setAppUsageState(MODE_RESTRICTED, Action.RESTORE); 346 } 347 348 @Test performBackup_backupDeviceBuildInformation()349 public void performBackup_backupDeviceBuildInformation() throws Exception { 350 final String[] fullPowerList = {"com.android.package"}; 351 doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); 352 doReturn(null).when(mPowerUsageFeatureProvider).getBuildMetadata1(mContext); 353 final String deviceMetadata = "device.metadata.test_device"; 354 doReturn(deviceMetadata).when(mPowerUsageFeatureProvider).getBuildMetadata2(mContext); 355 356 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 357 358 final InOrder inOrder = inOrder(mBackupDataOutput); 359 verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_BRAND, Build.BRAND); 360 verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_PRODUCT, Build.PRODUCT); 361 verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_MANUFACTURER, Build.MANUFACTURER); 362 verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_FINGERPRINT, Build.FINGERPRINT); 363 inOrder.verify(mBackupDataOutput, never()) 364 .writeEntityHeader(eq(BatteryBackupHelper.KEY_BUILD_METADATA_1), anyInt()); 365 verifyBackupData(inOrder, BatteryBackupHelper.KEY_BUILD_METADATA_2, deviceMetadata); 366 } 367 mockUid(int uid, String packageName)368 private void mockUid(int uid, String packageName) throws Exception { 369 doReturn(uid) 370 .when(mPackageManager) 371 .getPackageUid(packageName, PackageManager.GET_META_DATA); 372 } 373 mockBackupData(int dataSize, String dataKey)374 private void mockBackupData(int dataSize, String dataKey) { 375 doReturn(dataSize).when(mBackupDataInputStream).size(); 376 doReturn(dataKey).when(mBackupDataInputStream).getKey(); 377 } 378 verifyDumpHistoryData(String expectedResult)379 private void verifyDumpHistoryData(String expectedResult) { 380 BatteryBackupHelper.dumpHistoricalData(mContext, mPrintWriter); 381 assertThat(mStringWriter.toString().contains(expectedResult)).isTrue(); 382 } 383 verifyBackupData(String expectedResult)384 private void verifyBackupData(String expectedResult) throws Exception { 385 final byte[] expectedBytes = expectedResult.getBytes(); 386 final ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class); 387 final Set<String> expectedResultSet = 388 Set.of(expectedResult.split(BatteryBackupHelper.DELIMITER)); 389 390 verify(mBackupDataOutput) 391 .writeEntityHeader(BatteryBackupHelper.KEY_OPTIMIZATION_LIST, expectedBytes.length); 392 verify(mBackupDataOutput).writeEntityData(captor.capture(), eq(expectedBytes.length)); 393 final String actualResult = new String(captor.getValue()); 394 final Set<String> actualResultSet = 395 Set.of(actualResult.split(BatteryBackupHelper.DELIMITER)); 396 assertThat(actualResultSet).isEqualTo(expectedResultSet); 397 } 398 createTestingData( String packageName1, int uid1, String packageName2, String packageName3)399 private void createTestingData( 400 String packageName1, int uid1, String packageName2, String packageName3) 401 throws Exception { 402 // Sets the getInstalledApplications() method for testing. 403 final UserInfo userInfo = 404 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0); 405 doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt()); 406 final ApplicationInfo applicationInfo1 = new ApplicationInfo(); 407 applicationInfo1.enabled = true; 408 applicationInfo1.uid = uid1; 409 applicationInfo1.packageName = packageName1; 410 final ApplicationInfo applicationInfo2 = new ApplicationInfo(); 411 applicationInfo2.enabled = false; 412 applicationInfo2.uid = 2; 413 applicationInfo2.packageName = packageName2; 414 applicationInfo2.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; 415 final ApplicationInfo applicationInfo3 = new ApplicationInfo(); 416 applicationInfo3.enabled = false; 417 applicationInfo3.uid = 3; 418 applicationInfo3.packageName = packageName3; 419 applicationInfo3.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 420 doReturn( 421 new ParceledListSlice<ApplicationInfo>( 422 Arrays.asList( 423 applicationInfo1, applicationInfo2, applicationInfo3))) 424 .when(mIPackageManager) 425 .getInstalledApplications(anyLong(), anyInt()); 426 // Sets the AppOpsManager for checkOpNoThrow() method. 427 doReturn(AppOpsManager.MODE_ALLOWED) 428 .when(mAppOpsManager) 429 .checkOpNoThrow( 430 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, 431 applicationInfo1.uid, 432 applicationInfo1.packageName); 433 doReturn(AppOpsManager.MODE_IGNORED) 434 .when(mAppOpsManager) 435 .checkOpNoThrow( 436 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, 437 applicationInfo2.uid, 438 applicationInfo2.packageName); 439 mBatteryBackupHelper.mTestApplicationInfoList = 440 new ArraySet<>(Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3)); 441 } 442 verifyBackupData(InOrder inOrder, String dataKey, String dataContent)443 private void verifyBackupData(InOrder inOrder, String dataKey, String dataContent) 444 throws Exception { 445 final byte[] expectedBytes = dataContent.getBytes(); 446 inOrder.verify(mBackupDataOutput).writeEntityHeader(dataKey, expectedBytes.length); 447 inOrder.verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); 448 } 449 450 @Implements(UserHandle.class) 451 public static class ShadowUserHandle { 452 // Sets the default as thte OWNER role. 453 private static int sUid = 0; 454 setUid(int uid)455 public static void setUid(int uid) { 456 sUid = uid; 457 } 458 459 @Implementation myUserId()460 public static int myUserId() { 461 return sUid; 462 } 463 464 @Resetter reset()465 public static void reset() { 466 sUid = 0; 467 } 468 } 469 } 470