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.anyString; 29 import static org.mockito.ArgumentMatchers.eq; 30 import static org.mockito.Mockito.doReturn; 31 import static org.mockito.Mockito.doThrow; 32 import static org.mockito.Mockito.inOrder; 33 import static org.mockito.Mockito.never; 34 import static org.mockito.Mockito.spy; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.verifyZeroInteractions; 37 38 import android.app.AppOpsManager; 39 import android.app.backup.BackupDataInputStream; 40 import android.app.backup.BackupDataOutput; 41 import android.content.Context; 42 import android.content.pm.ApplicationInfo; 43 import android.content.pm.IPackageManager; 44 import android.content.pm.PackageManager; 45 import android.content.pm.ParceledListSlice; 46 import android.content.pm.UserInfo; 47 import android.os.IDeviceIdleController; 48 import android.os.RemoteException; 49 import android.os.UserHandle; 50 import android.os.UserManager; 51 52 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 53 54 import org.junit.After; 55 import org.junit.Before; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 import org.mockito.ArgumentCaptor; 59 import org.mockito.InOrder; 60 import org.mockito.Mock; 61 import org.mockito.MockitoAnnotations; 62 import org.robolectric.RobolectricTestRunner; 63 import org.robolectric.RuntimeEnvironment; 64 import org.robolectric.annotation.Config; 65 import org.robolectric.annotation.Implementation; 66 import org.robolectric.annotation.Implements; 67 import org.robolectric.annotation.Resetter; 68 69 import java.util.Arrays; 70 import java.util.List; 71 import java.util.concurrent.TimeUnit; 72 73 @RunWith(RobolectricTestRunner.class) 74 @Config(shadows = {BatteryBackupHelperTest.ShadowUserHandle.class}) 75 public final class BatteryBackupHelperTest { 76 private static final String PACKAGE_NAME1 = "com.android.testing.1"; 77 private static final String PACKAGE_NAME2 = "com.android.testing.2"; 78 private static final String PACKAGE_NAME3 = "com.android.testing.3"; 79 80 private Context mContext; 81 private BatteryBackupHelper mBatteryBackupHelper; 82 83 @Mock 84 private PackageManager mPackageManager; 85 @Mock 86 private BackupDataOutput mBackupDataOutput; 87 @Mock 88 private BackupDataInputStream mBackupDataInputStream; 89 @Mock 90 private IDeviceIdleController mDeviceController; 91 @Mock 92 private IPackageManager mIPackageManager; 93 @Mock 94 private AppOpsManager mAppOpsManager; 95 @Mock 96 private UserManager mUserManager; 97 @Mock 98 private PowerAllowlistBackend mPowerAllowlistBackend; 99 @Mock 100 private BatteryOptimizeUtils mBatteryOptimizeUtils; 101 102 @Before setUp()103 public void setUp() throws Exception { 104 MockitoAnnotations.initMocks(this); 105 mContext = spy(RuntimeEnvironment.application); 106 doReturn(mContext).when(mContext).getApplicationContext(); 107 doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class); 108 doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); 109 doReturn(mPackageManager).when(mContext).getPackageManager(); 110 mBatteryBackupHelper = new BatteryBackupHelper(mContext); 111 mBatteryBackupHelper.mIDeviceIdleController = mDeviceController; 112 mBatteryBackupHelper.mIPackageManager = mIPackageManager; 113 mBatteryBackupHelper.mPowerAllowlistBackend = mPowerAllowlistBackend; 114 mBatteryBackupHelper.mBatteryOptimizeUtils = mBatteryOptimizeUtils; 115 mockUid(1001 /*fake uid*/, PACKAGE_NAME1); 116 mockUid(1002 /*fake uid*/, PACKAGE_NAME2); 117 mockUid(BatteryUtils.UID_NULL, PACKAGE_NAME3); 118 } 119 120 @After resetShadows()121 public void resetShadows() { 122 ShadowUserHandle.reset(); 123 } 124 125 @Test performBackup_nullPowerList_notBackupPowerList()126 public void performBackup_nullPowerList_notBackupPowerList() throws Exception { 127 doReturn(null).when(mDeviceController).getFullPowerWhitelist(); 128 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 129 130 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 131 } 132 133 @Test performBackup_emptyPowerList_notBackupPowerList()134 public void performBackup_emptyPowerList_notBackupPowerList() throws Exception { 135 doReturn(new String[0]).when(mDeviceController).getFullPowerWhitelist(); 136 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 137 138 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 139 } 140 141 @Test performBackup_remoteException_notBackupPowerList()142 public void performBackup_remoteException_notBackupPowerList() throws Exception { 143 doThrow(new RemoteException()).when(mDeviceController).getFullPowerWhitelist(); 144 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 145 146 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 147 } 148 149 @Test performBackup_oneFullPowerListElement_backupFullPowerListData()150 public void performBackup_oneFullPowerListElement_backupFullPowerListData() 151 throws Exception { 152 final String[] fullPowerList = {"com.android.package"}; 153 doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); 154 155 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 156 157 final byte[] expectedBytes = fullPowerList[0].getBytes(); 158 verify(mBackupDataOutput).writeEntityHeader( 159 BatteryBackupHelper.KEY_FULL_POWER_LIST, expectedBytes.length); 160 verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); 161 } 162 163 @Test performBackup_backupFullPowerListData()164 public void performBackup_backupFullPowerListData() throws Exception { 165 final String[] fullPowerList = {"com.android.package1", "com.android.package2"}; 166 doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); 167 168 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 169 170 final String expectedResult = fullPowerList[0] + DELIMITER + fullPowerList[1]; 171 final byte[] expectedBytes = expectedResult.getBytes(); 172 verify(mBackupDataOutput).writeEntityHeader( 173 BatteryBackupHelper.KEY_FULL_POWER_LIST, expectedBytes.length); 174 verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); 175 } 176 177 @Test performBackup_nonOwner_ignoreAllBackupAction()178 public void performBackup_nonOwner_ignoreAllBackupAction() throws Exception { 179 ShadowUserHandle.setUid(1); 180 final String[] fullPowerList = {"com.android.package"}; 181 doReturn(fullPowerList).when(mDeviceController).getFullPowerWhitelist(); 182 183 mBatteryBackupHelper.performBackup(null, mBackupDataOutput, null); 184 185 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 186 } 187 188 @Test backupOptimizationMode_nullInstalledApps_ignoreBackupOptimization()189 public void backupOptimizationMode_nullInstalledApps_ignoreBackupOptimization() 190 throws Exception { 191 final UserInfo userInfo = 192 new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0); 193 doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt()); 194 doThrow(new RuntimeException()) 195 .when(mIPackageManager) 196 .getInstalledApplications(anyInt(), anyInt()); 197 198 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, null); 199 200 verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt()); 201 } 202 203 @Test backupOptimizationMode_backupOptimizationMode()204 public void backupOptimizationMode_backupOptimizationMode() throws Exception { 205 final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1); 206 createTestingData(PACKAGE_NAME1, PACKAGE_NAME2, PACKAGE_NAME3); 207 208 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps); 209 210 // 2 for UNRESTRICTED mode and 1 for RESTRICTED mode. 211 final String expectedResult = PACKAGE_NAME1 + ":2," + PACKAGE_NAME2 + ":1,"; 212 verifyBackupData(expectedResult); 213 } 214 215 @Test backupOptimizationMode_backupOptimizationModeAndIgnoreSystemApp()216 public void backupOptimizationMode_backupOptimizationModeAndIgnoreSystemApp() 217 throws Exception { 218 final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1); 219 createTestingData(PACKAGE_NAME1, PACKAGE_NAME2, PACKAGE_NAME3); 220 // Sets "com.android.testing.1" as system app. 221 doReturn(true).when(mPowerAllowlistBackend).isSysAllowlisted(PACKAGE_NAME1); 222 doReturn(false).when(mPowerAllowlistBackend).isDefaultActiveApp(anyString()); 223 224 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps); 225 226 // "com.android.testing.2" for RESTRICTED mode. 227 final String expectedResult = PACKAGE_NAME2 + ":1,"; 228 verifyBackupData(expectedResult); 229 } 230 231 @Test backupOptimizationMode_backupOptimizationModeAndIgnoreDefaultApp()232 public void backupOptimizationMode_backupOptimizationModeAndIgnoreDefaultApp() 233 throws Exception { 234 final List<String> allowlistedApps = Arrays.asList(PACKAGE_NAME1); 235 createTestingData(PACKAGE_NAME1, PACKAGE_NAME2, PACKAGE_NAME3); 236 // Sets "com.android.testing.1" as device default app. 237 doReturn(true).when(mPowerAllowlistBackend).isDefaultActiveApp(PACKAGE_NAME1); 238 doReturn(false).when(mPowerAllowlistBackend).isSysAllowlisted(anyString()); 239 240 mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps); 241 242 // "com.android.testing.2" for RESTRICTED mode. 243 final String expectedResult = PACKAGE_NAME2 + ":1,"; 244 verifyBackupData(expectedResult); 245 } 246 247 @Test restoreEntity_nonOwner_notReadBackupData()248 public void restoreEntity_nonOwner_notReadBackupData() throws Exception { 249 ShadowUserHandle.setUid(1); 250 mockBackupData(30 /*dataSize*/, BatteryBackupHelper.KEY_OPTIMIZATION_LIST); 251 252 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 253 254 verifyZeroInteractions(mBackupDataInputStream); 255 } 256 257 @Test restoreEntity_zeroDataSize_notReadBackupData()258 public void restoreEntity_zeroDataSize_notReadBackupData() throws Exception { 259 final int zeroDataSize = 0; 260 mockBackupData(zeroDataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST); 261 262 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 263 264 verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt()); 265 } 266 267 @Test restoreEntity_incorrectDataKey_notReadBackupData()268 public void restoreEntity_incorrectDataKey_notReadBackupData() throws Exception { 269 final String incorrectDataKey = BatteryBackupHelper.KEY_FULL_POWER_LIST; 270 mockBackupData(30 /*dataSize*/, incorrectDataKey); 271 272 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 273 274 verify(mBackupDataInputStream, never()).read(any(), anyInt(), anyInt()); 275 } 276 277 @Test restoreEntity_readExpectedDataFromBackupData()278 public void restoreEntity_readExpectedDataFromBackupData() throws Exception { 279 final int dataSize = 30; 280 mockBackupData(dataSize, BatteryBackupHelper.KEY_OPTIMIZATION_LIST); 281 282 mBatteryBackupHelper.restoreEntity(mBackupDataInputStream); 283 284 final ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class); 285 verify(mBackupDataInputStream).read(captor.capture(), eq(0), eq(dataSize)); 286 assertThat(captor.getValue().length).isEqualTo(dataSize); 287 } 288 289 @Test restoreOptimizationMode_nullBytesData_skipRestore()290 public void restoreOptimizationMode_nullBytesData_skipRestore() throws Exception { 291 mBatteryBackupHelper.restoreOptimizationMode(new byte[0]); 292 verifyZeroInteractions(mBatteryOptimizeUtils); 293 294 mBatteryBackupHelper.restoreOptimizationMode("invalid data format".getBytes()); 295 verifyZeroInteractions(mBatteryOptimizeUtils); 296 297 mBatteryBackupHelper.restoreOptimizationMode(DELIMITER.getBytes()); 298 verifyZeroInteractions(mBatteryOptimizeUtils); 299 } 300 301 @Test restoreOptimizationMode_invalidModeFormat_skipRestore()302 public void restoreOptimizationMode_invalidModeFormat_skipRestore() throws Exception { 303 final String invalidNumberFormat = "google"; 304 final String packageModes = 305 PACKAGE_NAME1 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER + 306 PACKAGE_NAME2 + DELIMITER_MODE + invalidNumberFormat; 307 308 mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes()); 309 TimeUnit.SECONDS.sleep(1); 310 311 final InOrder inOrder = inOrder(mBatteryOptimizeUtils); 312 inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_RESTRICTED); 313 inOrder.verify(mBatteryOptimizeUtils, never()).setAppUsageState(anyInt()); 314 } 315 316 @Test restoreOptimizationMode_restoreExpectedModes()317 public void restoreOptimizationMode_restoreExpectedModes() throws Exception { 318 final String packageModes = 319 PACKAGE_NAME1 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER + 320 PACKAGE_NAME2 + DELIMITER_MODE + MODE_UNRESTRICTED + DELIMITER + 321 PACKAGE_NAME3 + DELIMITER_MODE + MODE_RESTRICTED + DELIMITER; 322 323 mBatteryBackupHelper.restoreOptimizationMode(packageModes.getBytes()); 324 TimeUnit.SECONDS.sleep(1); 325 326 final InOrder inOrder = inOrder(mBatteryOptimizeUtils); 327 inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_RESTRICTED); 328 inOrder.verify(mBatteryOptimizeUtils).setAppUsageState(MODE_UNRESTRICTED); 329 inOrder.verify(mBatteryOptimizeUtils, never()).setAppUsageState(MODE_RESTRICTED); 330 } 331 mockUid(int uid, String packageName)332 private void mockUid(int uid, String packageName) throws Exception { 333 doReturn(uid).when(mPackageManager) 334 .getPackageUid(packageName, PackageManager.GET_META_DATA); 335 } 336 mockBackupData(int dataSize, String dataKey)337 private void mockBackupData(int dataSize, String dataKey) { 338 doReturn(dataSize).when(mBackupDataInputStream).size(); 339 doReturn(dataKey).when(mBackupDataInputStream).getKey(); 340 } 341 verifyBackupData(String expectedResult)342 private void verifyBackupData(String expectedResult) throws Exception { 343 final byte[] expectedBytes = expectedResult.getBytes(); 344 verify(mBackupDataOutput).writeEntityHeader( 345 BatteryBackupHelper.KEY_OPTIMIZATION_LIST, expectedBytes.length); 346 verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length); 347 } 348 createTestingData( String packageName1, String packageName2, String packageName3)349 private void createTestingData( 350 String packageName1, String packageName2, String packageName3) throws Exception { 351 // Sets the getInstalledApplications() method for testing. 352 final UserInfo userInfo = 353 new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0); 354 doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt()); 355 final ApplicationInfo applicationInfo1 = new ApplicationInfo(); 356 applicationInfo1.enabled = true; 357 applicationInfo1.uid = 1; 358 applicationInfo1.packageName = packageName1; 359 final ApplicationInfo applicationInfo2 = new ApplicationInfo(); 360 applicationInfo2.enabled = false; 361 applicationInfo2.uid = 2; 362 applicationInfo2.packageName = packageName2; 363 applicationInfo2.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; 364 final ApplicationInfo applicationInfo3 = new ApplicationInfo(); 365 applicationInfo3.enabled = false; 366 applicationInfo3.uid = 3; 367 applicationInfo3.packageName = packageName3; 368 applicationInfo3.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 369 doReturn(new ParceledListSlice<ApplicationInfo>( 370 Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3))) 371 .when(mIPackageManager) 372 .getInstalledApplications(anyInt(), anyInt()); 373 // Sets the AppOpsManager for checkOpNoThrow() method. 374 doReturn(AppOpsManager.MODE_ALLOWED) 375 .when(mAppOpsManager) 376 .checkOpNoThrow( 377 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, 378 applicationInfo1.uid, 379 applicationInfo1.packageName); 380 doReturn(AppOpsManager.MODE_IGNORED) 381 .when(mAppOpsManager) 382 .checkOpNoThrow( 383 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, 384 applicationInfo2.uid, 385 applicationInfo2.packageName); 386 } 387 388 @Implements(UserHandle.class) 389 public static class ShadowUserHandle { 390 // Sets the default as thte OWNER role. 391 private static int sUid = 0; 392 setUid(int uid)393 public static void setUid(int uid) { 394 sUid = uid; 395 } 396 397 @Implementation myUserId()398 public static int myUserId() { 399 return sUid; 400 } 401 402 @Resetter reset()403 public static void reset() { 404 sUid = 0; 405 } 406 } 407 } 408