1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.fuelgauge; 18 19 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_OPTIMIZED; 20 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED; 21 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.mockito.Mockito.anyInt; 29 import static org.mockito.Mockito.anyLong; 30 import static org.mockito.Mockito.anyString; 31 import static org.mockito.Mockito.doReturn; 32 import static org.mockito.Mockito.doThrow; 33 import static org.mockito.Mockito.inOrder; 34 import static org.mockito.Mockito.never; 35 import static org.mockito.Mockito.spy; 36 import static org.mockito.Mockito.verify; 37 import static org.mockito.Mockito.verifyNoInteractions; 38 import static org.mockito.Mockito.verifyNoMoreInteractions; 39 import static org.mockito.Mockito.when; 40 41 import android.app.AppOpsManager; 42 import android.content.Context; 43 import android.content.pm.ApplicationInfo; 44 import android.content.pm.IPackageManager; 45 import android.content.pm.PackageManager; 46 import android.content.pm.ParceledListSlice; 47 import android.content.pm.UserInfo; 48 import android.os.UserManager; 49 import android.util.ArraySet; 50 51 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; 52 import com.android.settingslib.datastore.DataChangeReason; 53 import com.android.settingslib.datastore.Observer; 54 import com.android.settingslib.fuelgauge.PowerAllowlistBackend; 55 56 import com.google.common.util.concurrent.MoreExecutors; 57 58 import org.junit.Before; 59 import org.junit.Test; 60 import org.junit.runner.RunWith; 61 import org.mockito.InOrder; 62 import org.mockito.Mock; 63 import org.mockito.MockitoAnnotations; 64 import org.robolectric.RobolectricTestRunner; 65 import org.robolectric.RuntimeEnvironment; 66 67 import java.util.Arrays; 68 import java.util.concurrent.TimeUnit; 69 70 @RunWith(RobolectricTestRunner.class) 71 public class BatteryOptimizeUtilsTest { 72 73 private static final int UID = 12345; 74 private static final String PACKAGE_NAME = "com.android.app"; 75 76 @Mock private BatteryUtils mMockBatteryUtils; 77 @Mock private AppOpsManager mMockAppOpsManager; 78 @Mock private PowerAllowlistBackend mMockBackend; 79 @Mock private IPackageManager mMockIPackageManager; 80 @Mock private UserManager mMockUserManager; 81 @Mock private Observer mObserver; 82 83 private Context mContext; 84 private BatteryOptimizeUtils mBatteryOptimizeUtils; 85 private BatterySettingsStorage mBatterySettingsStorage; 86 87 @Before setUp()88 public void setUp() { 89 MockitoAnnotations.initMocks(this); 90 mContext = spy(RuntimeEnvironment.application); 91 mBatterySettingsStorage = BatterySettingsStorage.get(mContext); 92 mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor()); 93 mBatteryOptimizeUtils = spy(new BatteryOptimizeUtils(mContext, UID, PACKAGE_NAME)); 94 mBatteryOptimizeUtils.mAppOpsManager = mMockAppOpsManager; 95 mBatteryOptimizeUtils.mBatteryUtils = mMockBatteryUtils; 96 mBatteryOptimizeUtils.mPowerAllowListBackend = mMockBackend; 97 // Sets the default mode as MODE_RESTRICTED. 98 mBatteryOptimizeUtils.mMode = AppOpsManager.MODE_IGNORED; 99 mBatteryOptimizeUtils.mAllowListed = false; 100 doReturn(mMockUserManager).when(mContext).getSystemService(UserManager.class); 101 } 102 103 @Test testGetAppOptimizationMode_returnRestricted()104 public void testGetAppOptimizationMode_returnRestricted() { 105 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(false); 106 when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) 107 .thenReturn(AppOpsManager.MODE_IGNORED); 108 109 assertThat(mBatteryOptimizeUtils.getAppOptimizationMode()).isEqualTo(MODE_RESTRICTED); 110 } 111 112 @Test testGetAppOptimizationMode_returnUnrestricted()113 public void testGetAppOptimizationMode_returnUnrestricted() { 114 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true); 115 when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) 116 .thenReturn(AppOpsManager.MODE_ALLOWED); 117 118 assertThat(mBatteryOptimizeUtils.getAppOptimizationMode()).isEqualTo(MODE_UNRESTRICTED); 119 } 120 121 @Test testGetAppOptimizationMode_returnOptimized()122 public void testGetAppOptimizationMode_returnOptimized() { 123 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(false); 124 when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) 125 .thenReturn(AppOpsManager.MODE_ALLOWED); 126 127 assertThat(mBatteryOptimizeUtils.getAppOptimizationMode()).isEqualTo(MODE_OPTIMIZED); 128 } 129 130 @Test testIsSystemOrDefaultApp_isSystemOrDefaultApp_returnTrue()131 public void testIsSystemOrDefaultApp_isSystemOrDefaultApp_returnTrue() { 132 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true); 133 when(mMockBackend.isDefaultActiveApp(anyString(), anyInt())).thenReturn(true); 134 135 assertThat(mBatteryOptimizeUtils.isSystemOrDefaultApp()).isTrue(); 136 } 137 138 @Test testIsSystemOrDefaultApp_notSystemOrDefaultApp_returnFalse()139 public void testIsSystemOrDefaultApp_notSystemOrDefaultApp_returnFalse() { 140 assertThat(mBatteryOptimizeUtils.isSystemOrDefaultApp()).isFalse(); 141 } 142 143 @Test isDisabledForOptimizeModeOnly_invalidPackageName_returnTrue()144 public void isDisabledForOptimizeModeOnly_invalidPackageName_returnTrue() { 145 final BatteryOptimizeUtils testBatteryOptimizeUtils = 146 new BatteryOptimizeUtils(mContext, UID, null); 147 148 assertThat(testBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()).isTrue(); 149 } 150 151 @Test isDisabledForOptimizeModeOnly_validPackageName_returnFalse()152 public void isDisabledForOptimizeModeOnly_validPackageName_returnFalse() { 153 assertThat(mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()).isFalse(); 154 } 155 156 @Test testSetAppUsageState_Restricted_verifyAction()157 public void testSetAppUsageState_Restricted_verifyAction() throws Exception { 158 // Sets the current mode as MODE_UNRESTRICTED. 159 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true); 160 when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) 161 .thenReturn(AppOpsManager.MODE_ALLOWED); 162 163 mBatteryOptimizeUtils.setAppUsageState(MODE_RESTRICTED, Action.UNKNOWN); 164 TimeUnit.SECONDS.sleep(1); 165 166 verifySetAppOptimizationMode(AppOpsManager.MODE_IGNORED, /* allowListed */ false); 167 verify(mObserver).onChanged(DataChangeReason.UPDATE); 168 } 169 170 @Test testSetAppUsageState_Unrestricted_verifyAction()171 public void testSetAppUsageState_Unrestricted_verifyAction() throws Exception { 172 // Sets the current mode as MODE_RESTRICTED. 173 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(false); 174 when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) 175 .thenReturn(AppOpsManager.MODE_IGNORED); 176 177 mBatteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED, Action.UNKNOWN); 178 TimeUnit.SECONDS.sleep(1); 179 180 verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ true); 181 verify(mObserver).onChanged(DataChangeReason.UPDATE); 182 } 183 184 @Test testSetAppUsageState_Optimized_verifyAction()185 public void testSetAppUsageState_Optimized_verifyAction() throws Exception { 186 // Sets the current mode as MODE_UNRESTRICTED. 187 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true); 188 when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) 189 .thenReturn(AppOpsManager.MODE_ALLOWED); 190 191 mBatteryOptimizeUtils.setAppUsageState(MODE_OPTIMIZED, Action.UNKNOWN); 192 TimeUnit.SECONDS.sleep(1); 193 194 verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); 195 verify(mObserver).onChanged(DataChangeReason.UPDATE); 196 } 197 198 @Test testSetAppUsageState_sameUnrestrictedMode_verifyNoAction()199 public void testSetAppUsageState_sameUnrestrictedMode_verifyNoAction() throws Exception { 200 // Sets the current mode as MODE_UNRESTRICTED. 201 when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true); 202 when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString())) 203 .thenReturn(AppOpsManager.MODE_ALLOWED); 204 205 mBatteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED, Action.UNKNOWN); 206 TimeUnit.SECONDS.sleep(1); 207 208 verify(mMockBatteryUtils, never()).setForceAppStandby(anyInt(), anyString(), anyInt()); 209 verify(mMockBackend, never()).addApp(anyString(), anyInt()); 210 verify(mMockBackend, never()).removeApp(anyString(), anyInt()); 211 verifyNoInteractions(mObserver); 212 } 213 214 @Test testGetInstalledApplications_returnEmptyArray()215 public void testGetInstalledApplications_returnEmptyArray() { 216 assertTrue( 217 BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager) 218 .isEmpty()); 219 } 220 221 @Test testGetInstalledApplications_returnNull()222 public void testGetInstalledApplications_returnNull() throws Exception { 223 final UserInfo userInfo = 224 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0); 225 doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt()); 226 doThrow(new RuntimeException()) 227 .when(mMockIPackageManager) 228 .getInstalledApplications(anyLong(), anyInt()); 229 230 assertNull(BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager)); 231 } 232 233 @Test testGetInstalledApplications_returnInstalledApps()234 public void testGetInstalledApplications_returnInstalledApps() throws Exception { 235 final UserInfo userInfo = 236 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0); 237 doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt()); 238 239 final ApplicationInfo applicationInfo1 = new ApplicationInfo(); 240 applicationInfo1.enabled = true; 241 applicationInfo1.uid = 1; 242 final ApplicationInfo applicationInfo2 = new ApplicationInfo(); 243 applicationInfo2.enabled = false; 244 applicationInfo2.uid = 2; 245 applicationInfo2.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; 246 final ApplicationInfo applicationInfo3 = new ApplicationInfo(); 247 applicationInfo3.enabled = false; 248 applicationInfo3.uid = 3; 249 applicationInfo3.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 250 final ApplicationInfo applicationInfo4 = new ApplicationInfo(); 251 applicationInfo4.enabled = true; 252 applicationInfo4.uid = 4; 253 applicationInfo4.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; 254 doReturn( 255 new ParceledListSlice<ApplicationInfo>( 256 Arrays.asList( 257 applicationInfo1, 258 applicationInfo2, 259 applicationInfo3, 260 applicationInfo4))) 261 .when(mMockIPackageManager) 262 .getInstalledApplications(anyLong(), anyInt()); 263 264 final ArraySet<ApplicationInfo> applications = 265 BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager); 266 assertThat(applications.size()).isEqualTo(3); 267 // applicationInfo3 should be filtered. 268 assertTrue(applications.contains(applicationInfo1)); 269 assertTrue(applications.contains(applicationInfo2)); 270 assertFalse(applications.contains(applicationInfo3)); 271 assertTrue(applications.contains(applicationInfo4)); 272 } 273 274 @Test testResetAppOptimizationModeInternal_Optimized_verifyAction()275 public void testResetAppOptimizationModeInternal_Optimized_verifyAction() throws Exception { 276 runTestForResetWithMode( 277 AppOpsManager.MODE_ALLOWED, /* allowListed */ 278 false, 279 /* isSystemOrDefaultApp */ false); 280 281 verifyNoInteractions(mMockBatteryUtils); 282 283 final InOrder inOrder = inOrder(mMockBackend); 284 inOrder.verify(mMockBackend).refreshList(); 285 inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME, UID); 286 verifyNoMoreInteractions(mMockBackend); 287 } 288 289 @Test testResetAppOptimizationModeInternal_SystemOrDefault_verifyAction()290 public void testResetAppOptimizationModeInternal_SystemOrDefault_verifyAction() 291 throws Exception { 292 runTestForResetWithMode( 293 AppOpsManager.MODE_ALLOWED, /* allowListed */ 294 true, 295 /* isSystemOrDefaultApp */ true); 296 297 verifyNoInteractions(mMockBatteryUtils); 298 299 final InOrder inOrder = inOrder(mMockBackend); 300 inOrder.verify(mMockBackend).refreshList(); 301 inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME, UID); 302 inOrder.verify(mMockBackend).isSysAllowlisted(PACKAGE_NAME); 303 verifyNoMoreInteractions(mMockBackend); 304 verify(mObserver).onChanged(DataChangeReason.DELETE); 305 } 306 307 @Test testResetAppOptimizationModeInternal_Restricted_verifyAction()308 public void testResetAppOptimizationModeInternal_Restricted_verifyAction() throws Exception { 309 runTestForResetWithMode( 310 AppOpsManager.MODE_IGNORED, /* allowListed */ 311 false, 312 /* isSystemOrDefaultApp */ false); 313 314 verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); 315 verify(mObserver).onChanged(DataChangeReason.DELETE); 316 } 317 318 @Test testResetAppOptimizationModeInternal_Unrestricted_verifyAction()319 public void testResetAppOptimizationModeInternal_Unrestricted_verifyAction() throws Exception { 320 runTestForResetWithMode( 321 AppOpsManager.MODE_ALLOWED, /* allowListed */ 322 true, 323 /* isSystemOrDefaultApp */ false); 324 325 verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); 326 verify(mObserver).onChanged(DataChangeReason.DELETE); 327 } 328 runTestForResetWithMode( int appStandbyMode, boolean allowListed, boolean isSystemOrDefaultApp)329 private void runTestForResetWithMode( 330 int appStandbyMode, boolean allowListed, boolean isSystemOrDefaultApp) 331 throws Exception { 332 final UserInfo userInfo = 333 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0); 334 doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt()); 335 final ApplicationInfo applicationInfo = new ApplicationInfo(); 336 applicationInfo.uid = UID; 337 applicationInfo.packageName = PACKAGE_NAME; 338 applicationInfo.enabled = true; 339 doReturn(new ParceledListSlice<ApplicationInfo>(Arrays.asList(applicationInfo))) 340 .when(mMockIPackageManager) 341 .getInstalledApplications(anyLong(), anyInt()); 342 343 doReturn(appStandbyMode) 344 .when(mMockAppOpsManager) 345 .checkOpNoThrow(anyInt(), anyInt(), anyString()); 346 doReturn(allowListed).when(mMockBackend).isAllowlisted(anyString(), anyInt()); 347 doReturn(isSystemOrDefaultApp).when(mMockBackend).isSysAllowlisted(anyString()); 348 doReturn(isSystemOrDefaultApp).when(mMockBackend).isDefaultActiveApp(anyString(), anyInt()); 349 350 BatteryOptimizeUtils.resetAppOptimizationModeInternal( 351 mContext, 352 mMockIPackageManager, 353 mMockAppOpsManager, 354 mMockBackend, 355 mMockBatteryUtils); 356 TimeUnit.SECONDS.sleep(1); 357 } 358 verifySetAppOptimizationMode(int appStandbyMode, boolean allowListed)359 private void verifySetAppOptimizationMode(int appStandbyMode, boolean allowListed) { 360 verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, appStandbyMode); 361 if (allowListed) { 362 verify(mMockBackend).addApp(PACKAGE_NAME, UID); 363 } else { 364 verify(mMockBackend).removeApp(PACKAGE_NAME, UID); 365 } 366 } 367 } 368