1 /* 2 * Copyright (C) 2016 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 package com.android.settings.fuelgauge; 17 18 import static com.android.settings.fuelgauge.PowerUsageSummary.BATTERY_INFO_LOADER; 19 import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADVANCED_BATTERY; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.mockito.ArgumentMatchers.any; 24 import static org.mockito.ArgumentMatchers.anyInt; 25 import static org.mockito.ArgumentMatchers.anyLong; 26 import static org.mockito.ArgumentMatchers.eq; 27 import static org.mockito.Mockito.doAnswer; 28 import static org.mockito.Mockito.doNothing; 29 import static org.mockito.Mockito.doReturn; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.spy; 33 import static org.mockito.Mockito.times; 34 import static org.mockito.Mockito.verify; 35 import static org.mockito.Mockito.when; 36 37 import android.content.ContentResolver; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.os.Bundle; 41 import android.provider.Settings; 42 import android.view.Menu; 43 import android.view.MenuInflater; 44 import android.view.MenuItem; 45 import android.view.View; 46 import android.widget.TextView; 47 48 import androidx.annotation.VisibleForTesting; 49 import androidx.loader.app.LoaderManager; 50 import androidx.preference.PreferenceScreen; 51 52 import com.android.internal.os.BatterySipper; 53 import com.android.internal.os.BatteryStatsHelper; 54 import com.android.settings.R; 55 import com.android.settings.SettingsActivity; 56 import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; 57 import com.android.settings.testutils.FakeFeatureFactory; 58 import com.android.settings.testutils.XmlTestUtils; 59 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; 60 import com.android.settingslib.widget.LayoutPreference; 61 62 import org.junit.Before; 63 import org.junit.BeforeClass; 64 import org.junit.Test; 65 import org.junit.runner.RunWith; 66 import org.mockito.Answers; 67 import org.mockito.ArgumentCaptor; 68 import org.mockito.Mock; 69 import org.mockito.MockitoAnnotations; 70 import org.mockito.invocation.InvocationOnMock; 71 import org.mockito.stubbing.Answer; 72 import org.robolectric.Robolectric; 73 import org.robolectric.RobolectricTestRunner; 74 import org.robolectric.RuntimeEnvironment; 75 import org.robolectric.util.ReflectionHelpers; 76 77 import java.util.ArrayList; 78 import java.util.List; 79 80 // TODO: Improve this test class so that it starts up the real activity and fragment. 81 @RunWith(RobolectricTestRunner.class) 82 public class PowerUsageSummaryTest { 83 84 private static final int UID = 123; 85 private static final int UID_2 = 234; 86 private static final int POWER_MAH = 100; 87 private static final long TIME_SINCE_LAST_FULL_CHARGE_MS = 120 * 60 * 1000; 88 private static final long TIME_SINCE_LAST_FULL_CHARGE_US = 89 TIME_SINCE_LAST_FULL_CHARGE_MS * 1000; 90 private static final long USAGE_TIME_MS = 65 * 60 * 1000; 91 private static final double TOTAL_POWER = 200; 92 private static final String NEW_ML_EST_SUFFIX = "(New ML est)"; 93 private static final String OLD_EST_SUFFIX = "(Old est)"; 94 private static Intent sAdditionalBatteryInfoIntent; 95 96 @BeforeClass beforeClass()97 public static void beforeClass() { 98 sAdditionalBatteryInfoIntent = new Intent("com.example.app.ADDITIONAL_BATTERY_INFO"); 99 } 100 101 @Mock 102 private BatterySipper mNormalBatterySipper; 103 @Mock 104 private BatterySipper mScreenBatterySipper; 105 @Mock 106 private BatterySipper mCellBatterySipper; 107 @Mock 108 private LayoutPreference mBatteryLayoutPref; 109 @Mock 110 private TextView mBatteryPercentText; 111 @Mock 112 private TextView mSummary1; 113 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 114 private BatteryStatsHelper mBatteryHelper; 115 @Mock 116 private SettingsActivity mSettingsActivity; 117 @Mock 118 private LoaderManager mLoaderManager; 119 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 120 private Menu mMenu; 121 @Mock 122 private MenuInflater mMenuInflater; 123 @Mock 124 private MenuItem mAdvancedPageMenu; 125 @Mock 126 private BatteryInfo mBatteryInfo; 127 @Mock 128 private ContentResolver mContentResolver; 129 @Mock 130 private BatteryBroadcastReceiver mBatteryBroadcastReceiver; 131 @Mock 132 private VisibilityLoggerMixin mVisibilityLoggerMixin; 133 @Mock 134 private PreferenceScreen mPreferenceScreen; 135 136 private List<BatterySipper> mUsageList; 137 private Context mRealContext; 138 private TestFragment mFragment; 139 private FakeFeatureFactory mFeatureFactory; 140 private BatteryMeterView mBatteryMeterView; 141 private PowerGaugePreference mScreenUsagePref; 142 private PowerGaugePreference mLastFullChargePref; 143 private Intent mIntent; 144 145 @Before setUp()146 public void setUp() { 147 MockitoAnnotations.initMocks(this); 148 149 mRealContext = spy(RuntimeEnvironment.application); 150 mFeatureFactory = FakeFeatureFactory.setupForTest(); 151 mScreenUsagePref = new PowerGaugePreference(mRealContext); 152 mLastFullChargePref = new PowerGaugePreference(mRealContext); 153 mFragment = spy(new TestFragment(mRealContext)); 154 mFragment.initFeatureProvider(); 155 mBatteryMeterView = new BatteryMeterView(mRealContext); 156 mBatteryMeterView.mDrawable = new BatteryMeterView.BatteryMeterDrawable(mRealContext, 0); 157 doNothing().when(mFragment).restartBatteryStatsLoader(anyInt()); 158 doReturn(mock(LoaderManager.class)).when(mFragment).getLoaderManager(); 159 doReturn(MENU_ADVANCED_BATTERY).when(mAdvancedPageMenu).getItemId(); 160 161 when(mFragment.getActivity()).thenReturn(mSettingsActivity); 162 when(mFeatureFactory.powerUsageFeatureProvider.getAdditionalBatteryInfoIntent()) 163 .thenReturn(sAdditionalBatteryInfoIntent); 164 when(mBatteryHelper.getTotalPower()).thenReturn(TOTAL_POWER); 165 when(mBatteryHelper.getStats().computeBatteryRealtime(anyLong(), anyInt())) 166 .thenReturn(TIME_SINCE_LAST_FULL_CHARGE_US); 167 168 when(mNormalBatterySipper.getUid()).thenReturn(UID); 169 mNormalBatterySipper.totalPowerMah = POWER_MAH; 170 mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; 171 172 mCellBatterySipper.drainType = BatterySipper.DrainType.CELL; 173 mCellBatterySipper.totalPowerMah = POWER_MAH; 174 175 when(mBatteryLayoutPref.findViewById(R.id.summary1)).thenReturn(mSummary1); 176 when(mBatteryLayoutPref.findViewById(R.id.battery_percent)).thenReturn(mBatteryPercentText); 177 when(mBatteryLayoutPref.findViewById(R.id.battery_header_icon)) 178 .thenReturn(mBatteryMeterView); 179 mFragment.setBatteryLayoutPreference(mBatteryLayoutPref); 180 181 mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; 182 mScreenBatterySipper.usageTimeMs = USAGE_TIME_MS; 183 184 mUsageList = new ArrayList<>(); 185 mUsageList.add(mNormalBatterySipper); 186 mUsageList.add(mScreenBatterySipper); 187 mUsageList.add(mCellBatterySipper); 188 189 mFragment.mStatsHelper = mBatteryHelper; 190 when(mBatteryHelper.getUsageList()).thenReturn(mUsageList); 191 mFragment.mScreenUsagePref = mScreenUsagePref; 192 mFragment.mLastFullChargePref = mLastFullChargePref; 193 mFragment.mBatteryUtils = spy(new BatteryUtils(mRealContext)); 194 ReflectionHelpers.setField(mFragment, "mVisibilityLoggerMixin", mVisibilityLoggerMixin); 195 ReflectionHelpers.setField(mFragment, "mBatteryBroadcastReceiver", 196 mBatteryBroadcastReceiver); 197 doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); 198 when(mFragment.getContentResolver()).thenReturn(mContentResolver); 199 } 200 201 @Test updateLastFullChargePreference_noAverageTime_showLastFullChargeSummary()202 public void updateLastFullChargePreference_noAverageTime_showLastFullChargeSummary() { 203 mFragment.mBatteryInfo = null; 204 when(mFragment.getContext()).thenReturn(mRealContext); 205 doReturn(TIME_SINCE_LAST_FULL_CHARGE_MS).when( 206 mFragment.mBatteryUtils).calculateLastFullChargeTime(any(), anyLong()); 207 208 mFragment.updateLastFullChargePreference(); 209 210 assertThat(mLastFullChargePref.getTitle()).isEqualTo("Last full charge"); 211 assertThat(mLastFullChargePref.getSubtitle()).isEqualTo("2 hours ago"); 212 } 213 214 @Test updateLastFullChargePreference_hasAverageTime_showFullChargeLastSummary()215 public void updateLastFullChargePreference_hasAverageTime_showFullChargeLastSummary() { 216 mFragment.mBatteryInfo = mBatteryInfo; 217 mBatteryInfo.averageTimeToDischarge = TIME_SINCE_LAST_FULL_CHARGE_MS; 218 when(mFragment.getContext()).thenReturn(mRealContext); 219 220 mFragment.updateLastFullChargePreference(); 221 222 assertThat(mLastFullChargePref.getTitle()).isEqualTo("Full charge lasts about"); 223 assertThat(mLastFullChargePref.getSubtitle().toString()).isEqualTo("2 hr"); 224 } 225 226 @Test nonIndexableKeys_MatchPreferenceKeys()227 public void nonIndexableKeys_MatchPreferenceKeys() { 228 final Context context = RuntimeEnvironment.application; 229 final List<String> niks = 230 PowerUsageSummary.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(context); 231 232 final List<String> keys = 233 XmlTestUtils.getKeysFromPreferenceXml(context, R.xml.power_usage_summary); 234 235 assertThat(keys).containsAllIn(niks); 236 } 237 238 @Test restartBatteryTipLoader()239 public void restartBatteryTipLoader() { 240 //TODO: add policy logic here when BatteryTipPolicy is implemented 241 doReturn(mLoaderManager).when(mFragment).getLoaderManager(); 242 243 mFragment.restartBatteryTipLoader(); 244 245 verify(mLoaderManager) 246 .restartLoader(eq(PowerUsageSummary.BATTERY_TIP_LOADER), eq(Bundle.EMPTY), any()); 247 } 248 249 @Test showBothEstimates_summariesAreBothModified()250 public void showBothEstimates_summariesAreBothModified() { 251 when(mFeatureFactory.powerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(any())) 252 .thenReturn(true); 253 doAnswer(new Answer() { 254 @Override 255 public Object answer(InvocationOnMock invocation) { 256 return mRealContext.getString( 257 R.string.power_usage_old_debug, invocation.getArguments()[0]); 258 } 259 }).when(mFeatureFactory.powerUsageFeatureProvider).getOldEstimateDebugString(any()); 260 doAnswer(new Answer() { 261 @Override 262 public Object answer(InvocationOnMock invocation) { 263 return mRealContext.getString( 264 R.string.power_usage_enhanced_debug, invocation.getArguments()[0]); 265 } 266 }).when(mFeatureFactory.powerUsageFeatureProvider).getEnhancedEstimateDebugString(any()); 267 268 doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary1); 269 mFragment.onLongClick(new View(mRealContext)); 270 TextView summary1 = mFragment.mBatteryLayoutPref.findViewById(R.id.summary1); 271 Robolectric.flushBackgroundThreadScheduler(); 272 assertThat(summary1.getText().toString()).contains(NEW_ML_EST_SUFFIX); 273 assertThat(summary1.getText().toString()).contains(OLD_EST_SUFFIX); 274 } 275 276 @Test debugMode()277 public void debugMode() { 278 doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isEstimateDebugEnabled(); 279 280 mFragment.restartBatteryInfoLoader(); 281 ArgumentCaptor<View.OnLongClickListener> listener = ArgumentCaptor.forClass( 282 View.OnLongClickListener.class); 283 verify(mSummary1).setOnLongClickListener(listener.capture()); 284 285 // Calling the listener should disable it. 286 listener.getValue().onLongClick(mSummary1); 287 verify(mSummary1).setOnLongClickListener(null); 288 289 // Restarting the loader should reset the listener. 290 mFragment.restartBatteryInfoLoader(); 291 verify(mSummary1, times(2)).setOnLongClickListener(any(View.OnLongClickListener.class)); 292 } 293 294 @Test optionsMenu_advancedPageEnabled()295 public void optionsMenu_advancedPageEnabled() { 296 when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled()) 297 .thenReturn(true); 298 299 mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); 300 301 verify(mMenu).add(Menu.NONE, MENU_ADVANCED_BATTERY, Menu.NONE, 302 R.string.advanced_battery_title); 303 } 304 305 @Test optionsMenu_clickAdvancedPage_fireIntent()306 public void optionsMenu_clickAdvancedPage_fireIntent() { 307 final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); 308 doAnswer(invocation -> { 309 // Get the intent in which it has the app info bundle 310 mIntent = captor.getValue(); 311 return true; 312 }).when(mRealContext).startActivity(captor.capture()); 313 314 mFragment.onOptionsItemSelected(mAdvancedPageMenu); 315 316 assertThat(mIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo( 317 PowerUsageAdvanced.class.getName()); 318 assertThat( 319 mIntent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0)).isEqualTo( 320 R.string.advanced_battery_title); 321 } 322 323 @Test refreshUi_deviceRotate_doNotUpdateBatteryTip()324 public void refreshUi_deviceRotate_doNotUpdateBatteryTip() { 325 mFragment.mBatteryTipPreferenceController = mock(BatteryTipPreferenceController.class); 326 when(mFragment.mBatteryTipPreferenceController.needUpdate()).thenReturn(false); 327 mFragment.updateBatteryTipFlag(new Bundle()); 328 329 mFragment.refreshUi(BatteryBroadcastReceiver.BatteryUpdateType.MANUAL); 330 331 verify(mFragment, never()).restartBatteryTipLoader(); 332 } 333 334 @Test refreshUi_batteryLevelChanged_doNotUpdateBatteryTip()335 public void refreshUi_batteryLevelChanged_doNotUpdateBatteryTip() { 336 mFragment.mBatteryTipPreferenceController = mock(BatteryTipPreferenceController.class); 337 when(mFragment.mBatteryTipPreferenceController.needUpdate()).thenReturn(true); 338 mFragment.updateBatteryTipFlag(new Bundle()); 339 340 mFragment.refreshUi(BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_LEVEL); 341 342 verify(mFragment, never()).restartBatteryTipLoader(); 343 } 344 345 @Test refreshUi_tipNeedUpdate_updateBatteryTip()346 public void refreshUi_tipNeedUpdate_updateBatteryTip() { 347 mFragment.mBatteryTipPreferenceController = mock(BatteryTipPreferenceController.class); 348 when(mFragment.mBatteryTipPreferenceController.needUpdate()).thenReturn(true); 349 mFragment.updateBatteryTipFlag(new Bundle()); 350 351 mFragment.refreshUi(BatteryBroadcastReceiver.BatteryUpdateType.MANUAL); 352 353 verify(mFragment).restartBatteryTipLoader(); 354 } 355 356 @Test onResume_registerContentObserver()357 public void onResume_registerContentObserver() { 358 mFragment.onResume(); 359 360 verify(mContentResolver).registerContentObserver( 361 Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME), 362 false, 363 mFragment.mSettingsObserver); 364 } 365 366 @Test onPause_unregisterContentObserver()367 public void onPause_unregisterContentObserver() { 368 mFragment.onPause(); 369 370 verify(mContentResolver).unregisterContentObserver( 371 mFragment.mSettingsObserver); 372 } 373 374 @Test restartBatteryInfoLoader_contextNull_doNothing()375 public void restartBatteryInfoLoader_contextNull_doNothing() { 376 when(mFragment.getContext()).thenReturn(null); 377 when(mFragment.getLoaderManager()).thenReturn(mLoaderManager); 378 379 mFragment.restartBatteryInfoLoader(); 380 381 verify(mLoaderManager, never()).restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY, 382 mFragment.mBatteryInfoLoaderCallbacks); 383 } 384 385 public static class TestFragment extends PowerUsageSummary { 386 private Context mContext; 387 TestFragment(Context context)388 public TestFragment(Context context) { 389 mContext = context; 390 } 391 392 @Override getContext()393 public Context getContext() { 394 return mContext; 395 } 396 397 @Override getContentResolver()398 protected ContentResolver getContentResolver() { 399 // Override it so we can access this method in test 400 return super.getContentResolver(); 401 } 402 403 @Override showBothEstimates()404 void showBothEstimates() { 405 List<BatteryInfo> fakeBatteryInfo = new ArrayList<>(2); 406 BatteryInfo info1 = new BatteryInfo(); 407 info1.batteryLevel = 10; 408 info1.remainingTimeUs = 10000; 409 info1.discharging = true; 410 411 BatteryInfo info2 = new BatteryInfo(); 412 info2.batteryLevel = 10; 413 info2.remainingTimeUs = 10000; 414 info2.discharging = true; 415 416 fakeBatteryInfo.add(info1); 417 fakeBatteryInfo.add(info2); 418 updateViews(fakeBatteryInfo); 419 } 420 } 421 } 422