1 /* 2 * Copyright (C) 2017 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.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS; 20 import static com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; 21 22 import static com.google.common.truth.Truth.assertThat; 23 24 import static org.mockito.ArgumentMatchers.anyInt; 25 import static org.mockito.ArgumentMatchers.anyString; 26 import static org.mockito.ArgumentMatchers.nullable; 27 import static org.mockito.Mockito.doAnswer; 28 import static org.mockito.Mockito.doReturn; 29 import static org.mockito.Mockito.mock; 30 import static org.mockito.Mockito.spy; 31 import static org.mockito.Mockito.verify; 32 import static org.mockito.Mockito.verifyNoInteractions; 33 import static org.mockito.Mockito.when; 34 35 import android.app.AppOpsManager; 36 import android.app.settings.SettingsEnums; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.pm.ApplicationInfo; 40 import android.content.pm.InstallSourceInfo; 41 import android.content.pm.PackageManager; 42 import android.graphics.drawable.Drawable; 43 import android.os.Bundle; 44 import android.os.UserHandle; 45 46 import androidx.fragment.app.FragmentActivity; 47 import androidx.loader.app.LoaderManager; 48 import androidx.test.core.app.ApplicationProvider; 49 50 import com.android.settings.R; 51 import com.android.settings.SettingsActivity; 52 import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry; 53 import com.android.settings.fuelgauge.batteryusage.BatteryEntry; 54 import com.android.settings.fuelgauge.batteryusage.ConvertUtils; 55 import com.android.settings.testutils.FakeFeatureFactory; 56 import com.android.settings.testutils.shadow.ShadowEntityHeaderController; 57 import com.android.settings.widget.EntityHeaderController; 58 import com.android.settingslib.PrimarySwitchPreference; 59 import com.android.settingslib.applications.AppUtils; 60 import com.android.settingslib.applications.ApplicationsState; 61 import com.android.settingslib.applications.instantapps.InstantAppDataProvider; 62 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 63 import com.android.settingslib.widget.LayoutPreference; 64 65 import org.junit.After; 66 import org.junit.Before; 67 import org.junit.Rule; 68 import org.junit.Test; 69 import org.junit.runner.RunWith; 70 import org.mockito.Answers; 71 import org.mockito.ArgumentCaptor; 72 import org.mockito.Mock; 73 import org.mockito.junit.MockitoJUnit; 74 import org.mockito.junit.MockitoRule; 75 import org.mockito.stubbing.Answer; 76 import org.robolectric.RobolectricTestRunner; 77 import org.robolectric.annotation.Config; 78 import org.robolectric.util.ReflectionHelpers; 79 80 import java.util.concurrent.TimeUnit; 81 82 @RunWith(RobolectricTestRunner.class) 83 @Config( 84 shadows = { 85 ShadowEntityHeaderController.class, 86 com.android.settings.testutils.shadow.ShadowFragment.class, 87 }) 88 public class AdvancedPowerUsageDetailTest { 89 90 @Rule 91 public final MockitoRule mMockitoRule = MockitoJUnit.rule(); 92 93 private static final String APP_LABEL = "app label"; 94 private static final String SUMMARY = "summary"; 95 private static final String[] PACKAGE_NAME = {"com.android.app"}; 96 private static final String USAGE_PERCENT = "16%"; 97 private static final int ICON_ID = 123; 98 private static final int UID = 1; 99 private static final long FOREGROUND_TIME_MS = 444; 100 private static final long FOREGROUND_SERVICE_TIME_MS = 123; 101 private static final long BACKGROUND_TIME_MS = 100; 102 private static final long SCREEN_ON_TIME_MS = 321; 103 private static final String KEY_ALLOW_BACKGROUND_USAGE = "allow_background_usage"; 104 105 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 106 private FragmentActivity mActivity; 107 108 @Mock private EntityHeaderController mEntityHeaderController; 109 @Mock private LayoutPreference mHeaderPreference; 110 @Mock private ApplicationsState mState; 111 @Mock private ApplicationsState.AppEntry mAppEntry; 112 @Mock private BatteryEntry mBatteryEntry; 113 @Mock private PackageManager mPackageManager; 114 @Mock private InstallSourceInfo mInstallSourceInfo; 115 @Mock private AppOpsManager mAppOpsManager; 116 @Mock private LoaderManager mLoaderManager; 117 @Mock private BatteryOptimizeUtils mBatteryOptimizeUtils; 118 119 private Context mContext; 120 private PrimarySwitchPreference mAllowBackgroundUsagePreference; 121 private AdvancedPowerUsageDetail mFragment; 122 private SettingsActivity mTestActivity; 123 private FakeFeatureFactory mFeatureFactory; 124 private MetricsFeatureProvider mMetricsFeatureProvider; 125 private BatteryDiffEntry mBatteryDiffEntry; 126 private Bundle mBundle; 127 128 @Before setUp()129 public void setUp() { 130 mContext = spy(ApplicationProvider.getApplicationContext()); 131 when(mContext.getPackageName()).thenReturn("foo"); 132 mFeatureFactory = FakeFeatureFactory.setupForTest(); 133 mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider; 134 135 mFragment = spy(new AdvancedPowerUsageDetail()); 136 mBundle = spy(new Bundle()); 137 doReturn(mContext).when(mFragment).getContext(); 138 doReturn(mActivity).when(mFragment).getActivity(); 139 doReturn(SUMMARY).when(mFragment).getString(anyInt()); 140 doReturn(APP_LABEL).when(mBundle).getString(nullable(String.class)); 141 when(mFragment.getArguments()).thenReturn(mBundle); 142 doReturn(mLoaderManager).when(mFragment).getLoaderManager(); 143 144 ShadowEntityHeaderController.setUseMock(mEntityHeaderController); 145 doReturn(mEntityHeaderController) 146 .when(mEntityHeaderController) 147 .setButtonActions(anyInt(), anyInt()); 148 doReturn(mEntityHeaderController) 149 .when(mEntityHeaderController) 150 .setIcon(nullable(Drawable.class)); 151 doReturn(mEntityHeaderController) 152 .when(mEntityHeaderController) 153 .setIcon(nullable(ApplicationsState.AppEntry.class)); 154 doReturn(mEntityHeaderController) 155 .when(mEntityHeaderController) 156 .setLabel(nullable(String.class)); 157 doReturn(mEntityHeaderController) 158 .when(mEntityHeaderController) 159 .setLabel(nullable(String.class)); 160 doReturn(mEntityHeaderController) 161 .when(mEntityHeaderController) 162 .setLabel(nullable(ApplicationsState.AppEntry.class)); 163 doReturn(mEntityHeaderController) 164 .when(mEntityHeaderController) 165 .setSummary(nullable(String.class)); 166 167 when(mBatteryEntry.getUid()).thenReturn(UID); 168 when(mBatteryEntry.getLabel()).thenReturn(APP_LABEL); 169 when(mBatteryEntry.getTimeInForegroundMs()).thenReturn(FOREGROUND_TIME_MS); 170 when(mBatteryEntry.getTimeInForegroundServiceMs()).thenReturn(FOREGROUND_SERVICE_TIME_MS); 171 when(mBatteryEntry.getTimeInBackgroundMs()).thenReturn(BACKGROUND_TIME_MS); 172 mBatteryEntry.mIconId = ICON_ID; 173 174 mBatteryDiffEntry = 175 spy( 176 new BatteryDiffEntry( 177 mContext, 178 /* uid= */ UID, 179 /* userId= */ 0, 180 /* key= */ "key", 181 /* isHidden= */ false, 182 /* componentId= */ -1, 183 /* legacyPackageName= */ null, 184 /* legacyLabel= */ null, 185 /*consumerType*/ ConvertUtils.CONSUMER_TYPE_USER_BATTERY, 186 /* foregroundUsageTimeInMs= */ FOREGROUND_TIME_MS, 187 /* foregroundSerUsageTimeInMs= */ FOREGROUND_SERVICE_TIME_MS, 188 /* backgroundUsageTimeInMs= */ BACKGROUND_TIME_MS, 189 /* screenOnTimeInMs= */ SCREEN_ON_TIME_MS, 190 /* consumePower= */ 0, 191 /* foregroundUsageConsumePower= */ 0, 192 /* foregroundServiceUsageConsumePower= */ 0, 193 /* backgroundUsageConsumePower= */ 0, 194 /* cachedUsageConsumePower= */ 0)); 195 when(mBatteryDiffEntry.getAppLabel()).thenReturn(APP_LABEL); 196 when(mBatteryDiffEntry.getAppIconId()).thenReturn(ICON_ID); 197 198 mFragment.mHeaderPreference = mHeaderPreference; 199 mFragment.mState = mState; 200 mFragment.mBatteryOptimizeUtils = mBatteryOptimizeUtils; 201 mFragment.mLogStringBuilder = new StringBuilder(); 202 mAppEntry.info = mock(ApplicationInfo.class); 203 204 mTestActivity = spy(new SettingsActivity()); 205 doReturn(mPackageManager).when(mTestActivity).getPackageManager(); 206 doReturn(mPackageManager).when(mActivity).getPackageManager(); 207 doReturn(mAppOpsManager).when(mTestActivity).getSystemService(Context.APP_OPS_SERVICE); 208 209 final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); 210 211 Answer<Void> callable = 212 invocation -> { 213 mBundle = captor.getValue().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 214 System.out.println("mBundle = " + mBundle); 215 return null; 216 }; 217 doAnswer(callable) 218 .when(mActivity) 219 .startActivityAsUser(captor.capture(), nullable(UserHandle.class)); 220 doAnswer(callable).when(mActivity).startActivity(captor.capture()); 221 doAnswer(callable).when(mContext).startActivity(captor.capture()); 222 223 mAllowBackgroundUsagePreference = new PrimarySwitchPreference(mContext); 224 mAllowBackgroundUsagePreference.setKey(KEY_ALLOW_BACKGROUND_USAGE); 225 mFragment.mAllowBackgroundUsagePreference = mAllowBackgroundUsagePreference; 226 } 227 228 @After reset()229 public void reset() { 230 ShadowEntityHeaderController.reset(); 231 } 232 233 @Test setPreferenceScreenResId_returnNewLayout()234 public void setPreferenceScreenResId_returnNewLayout() { 235 assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail); 236 } 237 238 @Test initHeader_NoAppEntry_BuildByBundle()239 public void initHeader_NoAppEntry_BuildByBundle() { 240 mFragment.mAppEntry = null; 241 mFragment.initHeader(); 242 243 verify(mEntityHeaderController).setIcon(nullable(Drawable.class)); 244 verify(mEntityHeaderController).setLabel(APP_LABEL); 245 } 246 247 @Test initHeader_HasAppEntry_BuildByAppEntry()248 public void initHeader_HasAppEntry_BuildByAppEntry() { 249 ReflectionHelpers.setStaticField( 250 AppUtils.class, 251 "sInstantAppDataProvider", 252 new InstantAppDataProvider() { 253 @Override 254 public boolean isInstantApp(ApplicationInfo info) { 255 return false; 256 } 257 }); 258 mFragment.mAppEntry = mAppEntry; 259 mFragment.initHeader(); 260 261 verify(mEntityHeaderController).setIcon(mAppEntry); 262 verify(mEntityHeaderController).setLabel(mAppEntry); 263 verify(mEntityHeaderController).setIsInstantApp(false); 264 } 265 266 @Test initHeader_HasAppEntry_InstantApp()267 public void initHeader_HasAppEntry_InstantApp() { 268 ReflectionHelpers.setStaticField( 269 AppUtils.class, 270 "sInstantAppDataProvider", 271 new InstantAppDataProvider() { 272 @Override 273 public boolean isInstantApp(ApplicationInfo info) { 274 return true; 275 } 276 }); 277 mFragment.mAppEntry = mAppEntry; 278 mFragment.initHeader(); 279 280 verify(mEntityHeaderController).setIcon(mAppEntry); 281 verify(mEntityHeaderController).setLabel(mAppEntry); 282 verify(mEntityHeaderController).setIsInstantApp(true); 283 } 284 285 @Test startBatteryDetailPage_invalidToShowSummary_noFGBDData()286 public void startBatteryDetailPage_invalidToShowSummary_noFGBDData() { 287 mBundle.clear(); 288 AdvancedPowerUsageDetail.startBatteryDetailPage( 289 mActivity, mFragment, mBatteryEntry, USAGE_PERCENT); 290 291 assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID); 292 assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME)).isEqualTo(0); 293 assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME)).isEqualTo(0); 294 assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_SCREEN_ON_TIME)).isEqualTo(0); 295 assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT)) 296 .isEqualTo(USAGE_PERCENT); 297 } 298 299 @Test startBatteryDetailPage_showSummary_hasFGBDData()300 public void startBatteryDetailPage_showSummary_hasFGBDData() { 301 final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); 302 mBundle.clear(); 303 AdvancedPowerUsageDetail.startBatteryDetailPage( 304 mContext, 305 mFragment.getMetricsCategory(), 306 mBatteryDiffEntry, 307 USAGE_PERCENT, 308 /* slotInformation= */ null, 309 /* showTimeInformation= */ true, 310 /* anomalyHintPrefKey= */ null, 311 /* anomalyHintText= */ null); 312 313 verify(mContext).startActivity(captor.capture()); 314 assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID); 315 assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME)) 316 .isEqualTo(BACKGROUND_TIME_MS + FOREGROUND_SERVICE_TIME_MS); 317 assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME)) 318 .isEqualTo(FOREGROUND_TIME_MS); 319 assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_SCREEN_ON_TIME)) 320 .isEqualTo(SCREEN_ON_TIME_MS); 321 assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT)) 322 .isEqualTo(USAGE_PERCENT); 323 assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_SLOT_TIME)) 324 .isEqualTo(null); 325 } 326 327 328 @Test startBatteryDetailPage_noBatteryUsage_hasBasicData()329 public void startBatteryDetailPage_noBatteryUsage_hasBasicData() { 330 final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); 331 332 AdvancedPowerUsageDetail.startBatteryDetailPage( 333 mActivity, mFragment, PACKAGE_NAME[0], UserHandle.OWNER); 334 335 verify(mActivity).startActivity(captor.capture()); 336 337 assertThat( 338 captor.getValue() 339 .getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS) 340 .getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME)) 341 .isEqualTo(PACKAGE_NAME[0]); 342 343 assertThat( 344 captor.getValue() 345 .getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS) 346 .getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT)) 347 .isEqualTo("0%"); 348 } 349 350 @Test startBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName()351 public void startBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName() 352 throws PackageManager.NameNotFoundException { 353 mBundle.clear(); 354 doReturn(UID).when(mPackageManager).getPackageUid(PACKAGE_NAME[0], 0 /* no flag */); 355 356 AdvancedPowerUsageDetail.startBatteryDetailPage( 357 mActivity, mFragment, PACKAGE_NAME[0], UserHandle.OWNER); 358 359 assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID); 360 } 361 362 @Test initFooter_isValidPackageName_hasCorrectString()363 public void initFooter_isValidPackageName_hasCorrectString() { 364 when(mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()).thenReturn(true); 365 366 mFragment.initFooter(); 367 368 assertThat(mAllowBackgroundUsagePreference.getSummary().toString()) 369 .isEqualTo("This app requires optimized battery usage."); 370 } 371 372 @Test initFooter_isSystemOrDefaultApp_hasCorrectString()373 public void initFooter_isSystemOrDefaultApp_hasCorrectString() { 374 when(mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()).thenReturn(false); 375 when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true); 376 377 mFragment.initFooter(); 378 379 assertThat(mAllowBackgroundUsagePreference.getSummary().toString()) 380 .isEqualTo("This app requires unrestricted battery usage."); 381 } 382 383 @Test initFooter_hasCorrectString()384 public void initFooter_hasCorrectString() { 385 when(mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()).thenReturn(false); 386 when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(false); 387 388 mFragment.initFooter(); 389 390 assertThat(mAllowBackgroundUsagePreference.getSummary().toString()) 391 .isEqualTo("Enable for real-time updates, disable to save battery"); 392 } 393 394 @Test onPause_optimizationModeChanged_logPreference()395 public void onPause_optimizationModeChanged_logPreference() 396 throws PackageManager.NameNotFoundException, InterruptedException { 397 final String packageName = "testPackageName"; 398 final int restrictedMode = BatteryOptimizeUtils.MODE_RESTRICTED; 399 final int optimizedMode = BatteryOptimizeUtils.MODE_OPTIMIZED; 400 mFragment.mOptimizationMode = restrictedMode; 401 when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(restrictedMode); 402 when(mBatteryOptimizeUtils.getPackageName()).thenReturn(packageName); 403 when(mContext.getPackageManager()).thenReturn(mPackageManager); 404 when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(mInstallSourceInfo); 405 when(mInstallSourceInfo.getInitiatingPackageName()).thenReturn("com.android.vending"); 406 407 mFragment.onPreferenceChange(mAllowBackgroundUsagePreference, true); 408 verify(mBatteryOptimizeUtils).setAppUsageState(optimizedMode, Action.APPLY); 409 when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(optimizedMode); 410 mFragment.onPause(); 411 412 TimeUnit.SECONDS.sleep(1); 413 verify(mMetricsFeatureProvider) 414 .action( 415 SettingsEnums.LEAVE_APP_BATTERY_USAGE, 416 SettingsEnums.ACTION_APP_BATTERY_USAGE_ALLOW_BACKGROUND, 417 SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL, 418 packageName, 419 /* consumed battery */ 0); 420 } 421 422 @Test onPause_optimizationModeIsNotChanged_notInvokeLogging()423 public void onPause_optimizationModeIsNotChanged_notInvokeLogging() 424 throws PackageManager.NameNotFoundException, InterruptedException { 425 final int restrictedMode = BatteryOptimizeUtils.MODE_RESTRICTED; 426 final int optimizedMode = BatteryOptimizeUtils.MODE_OPTIMIZED; 427 mFragment.mOptimizationMode = restrictedMode; 428 when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(restrictedMode); 429 when(mContext.getPackageManager()).thenReturn(mPackageManager); 430 when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(mInstallSourceInfo); 431 when(mInstallSourceInfo.getInitiatingPackageName()).thenReturn("com.android.vending"); 432 433 mFragment.onPreferenceChange(mAllowBackgroundUsagePreference, true); 434 verify(mBatteryOptimizeUtils).setAppUsageState(optimizedMode, Action.APPLY); 435 when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(optimizedMode); 436 mFragment.onPreferenceChange(mAllowBackgroundUsagePreference, false); 437 verify(mBatteryOptimizeUtils).setAppUsageState(restrictedMode, Action.APPLY); 438 when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(restrictedMode); 439 mFragment.onPause(); 440 441 TimeUnit.SECONDS.sleep(1); 442 verifyNoInteractions(mMetricsFeatureProvider); 443 } 444 445 @Test shouldSkipForInitialSUW_returnTrue()446 public void shouldSkipForInitialSUW_returnTrue() { 447 assertThat(mFragment.shouldSkipForInitialSUW()).isTrue(); 448 } 449 } 450