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