/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.fuelgauge; import java.util.List; import org.robolectric.RuntimeEnvironment; import android.app.LoaderManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.content.ContentResolver; import android.os.PowerManager; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.core.PreferenceController; import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor; import com.android.settings.testutils.XmlTestUtils; import com.android.settingslib.BatteryInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; import java.util.ArrayList; import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADDITIONAL_BATTERY_INFO; import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_HIGH_POWER_APPS; import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_TOGGLE_APPS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Unit tests for {@link PowerUsageSummary}. */ // TODO: Improve this test class so that it starts up the real activity and fragment. @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, shadows = { SettingsShadowResources.class, SettingsShadowResources.SettingsShadowTheme.class, ShadowDynamicIndexableContentMonitor.class }) public class PowerUsageSummaryTest { private static final String[] PACKAGE_NAMES = {"com.app1", "com.app2"}; private static final String TIME_LEFT = "2h30min"; private static final String STUB_STRING = "stub_string"; private static final int BATTERY_LEVEL = 55; private static final int UID = 123; private static final int UID_2 = 234; private static final int POWER_MAH = 100; private static final long REMAINING_TIME_US = 100000; private static final long TIME_SINCE_LAST_FULL_CHARGE_MS = 120 * 60 * 1000; private static final long TIME_SINCE_LAST_FULL_CHARGE_US = TIME_SINCE_LAST_FULL_CHARGE_MS * 1000; private static final int DISCHARGE_AMOUNT = 100; private static final long USAGE_TIME_MS = 65 * 60 * 1000; private static final double TOTAL_POWER = 200; private static final double BATTERY_SCREEN_USAGE = 300; private static final double BATTERY_SYSTEM_USAGE = 600; private static final double BATTERY_OVERCOUNTED_USAGE = 500; private static final double PRECISION = 0.001; private static final double POWER_USAGE_PERCENTAGE = 50; private static final Intent ADDITIONAL_BATTERY_INFO_INTENT = new Intent("com.example.app.ADDITIONAL_BATTERY_INFO"); @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Menu mMenu; @Mock private MenuItem mAdditionalBatteryInfoMenu; @Mock private MenuItem mToggleAppsMenu; @Mock private MenuItem mHighPowerMenu; @Mock private MenuInflater mMenuInflater; @Mock private BatterySipper mNormalBatterySipper; @Mock private BatterySipper mScreenBatterySipper; @Mock private BatterySipper mCellBatterySipper; @Mock private LayoutPreference mBatteryLayoutPref; @Mock private TextView mBatteryPercentText; @Mock private TextView mSummary1; @Mock private BatteryInfo mBatteryInfo; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private BatteryStatsHelper mBatteryHelper; @Mock private PowerManager mPowerManager; @Mock private SettingsActivity mSettingsActivity; @Mock private LoaderManager mLoaderManager; @Mock private ContentResolver mContentResolver; private List mUsageList; private Context mRealContext; private TestFragment mFragment; private FakeFeatureFactory mFeatureFactory; private BatteryMeterView mBatteryMeterView; private PowerGaugePreference mPreference; private PowerGaugePreference mScreenUsagePref; private PowerGaugePreference mLastFullChargePref; private SparseArray> mAnomalySparseArray; @Before public void setUp() { MockitoAnnotations.initMocks(this); mRealContext = RuntimeEnvironment.application; FakeFeatureFactory.setupForTest(mContext); mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager); mPreference = new PowerGaugePreference(mRealContext); mScreenUsagePref = new PowerGaugePreference(mRealContext); mLastFullChargePref = new PowerGaugePreference(mRealContext); mFragment = spy(new TestFragment(mContext)); mFragment.initFeatureProvider(); mBatteryMeterView = new BatteryMeterView(mRealContext); mBatteryMeterView.mDrawable = new BatteryMeterView.BatteryMeterDrawable(mRealContext, 0); doNothing().when(mFragment).restartBatteryStatsLoader(); when(mFragment.getActivity()).thenReturn(mSettingsActivity); when(mAdditionalBatteryInfoMenu.getItemId()) .thenReturn(MENU_ADDITIONAL_BATTERY_INFO); when(mToggleAppsMenu.getItemId()).thenReturn(MENU_TOGGLE_APPS); when(mHighPowerMenu.getItemId()).thenReturn(MENU_HIGH_POWER_APPS); when(mFeatureFactory.powerUsageFeatureProvider.getAdditionalBatteryInfoIntent()) .thenReturn(ADDITIONAL_BATTERY_INFO_INTENT); when(mBatteryHelper.getTotalPower()).thenReturn(TOTAL_POWER); when(mBatteryHelper.getStats().computeBatteryRealtime(anyLong(), anyInt())).thenReturn( TIME_SINCE_LAST_FULL_CHARGE_US); when(mNormalBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES); when(mNormalBatterySipper.getUid()).thenReturn(UID); mNormalBatterySipper.totalPowerMah = POWER_MAH; mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; mCellBatterySipper.drainType = BatterySipper.DrainType.CELL; mCellBatterySipper.totalPowerMah = POWER_MAH; when(mBatteryLayoutPref.findViewById(R.id.summary1)).thenReturn(mSummary1); when(mBatteryLayoutPref.findViewById(R.id.battery_percent)).thenReturn(mBatteryPercentText); when(mBatteryLayoutPref.findViewById(R.id.battery_header_icon)) .thenReturn(mBatteryMeterView); mFragment.setBatteryLayoutPreference(mBatteryLayoutPref); mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; mScreenBatterySipper.usageTimeMs = USAGE_TIME_MS; mUsageList = new ArrayList<>(); mUsageList.add(mNormalBatterySipper); mUsageList.add(mScreenBatterySipper); mUsageList.add(mCellBatterySipper); mFragment.mStatsHelper = mBatteryHelper; when(mBatteryHelper.getUsageList()).thenReturn(mUsageList); mFragment.mScreenUsagePref = mScreenUsagePref; mFragment.mLastFullChargePref = mLastFullChargePref; mFragment.mBatteryUtils = spy(new BatteryUtils(mRealContext)); mBatteryInfo.batteryLevel = BATTERY_LEVEL; } @Test public void testOptionsMenu_additionalBatteryInfoEnabled() { when(mFeatureFactory.powerUsageFeatureProvider.isAdditionalBatteryInfoEnabled()) .thenReturn(true); mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); verify(mMenu).add(Menu.NONE, MENU_ADDITIONAL_BATTERY_INFO, Menu.NONE, R.string.additional_battery_info); mFragment.onOptionsItemSelected(mAdditionalBatteryInfoMenu); assertThat(mFragment.mStartActivityCalled).isTrue(); assertThat(mFragment.mStartActivityIntent).isEqualTo(ADDITIONAL_BATTERY_INFO_INTENT); } @Test public void testOptionsMenu_additionalBatteryInfoDisabled() { when(mFeatureFactory.powerUsageFeatureProvider.isAdditionalBatteryInfoEnabled()) .thenReturn(false); mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); verify(mMenu, never()).add(Menu.NONE, MENU_ADDITIONAL_BATTERY_INFO, Menu.NONE, R.string.additional_battery_info); } @Test public void testOptionsMenu_menuHighPower_metricEventInvoked() { mFragment.onOptionsItemSelected(mHighPowerMenu); verify(mFeatureFactory.metricsFeatureProvider).action(mContext, MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION); } @Test public void testOptionsMenu_menuAdditionalBattery_metricEventInvoked() { mFragment.onOptionsItemSelected(mAdditionalBatteryInfoMenu); verify(mFeatureFactory.metricsFeatureProvider).action(mContext, MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_USAGE_ALERTS); } @Test public void testOptionsMenu_menuAppToggle_metricEventInvoked() { mFragment.onOptionsItemSelected(mToggleAppsMenu); mFragment.mShowAllApps = false; verify(mFeatureFactory.metricsFeatureProvider).action(mContext, MetricsProto.MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, true); } @Test public void testOptionsMenu_toggleAppsEnabled() { when(mFeatureFactory.powerUsageFeatureProvider.isPowerAccountingToggleEnabled()) .thenReturn(true); mFragment.mShowAllApps = false; mFragment.onCreateOptionsMenu(mMenu, mMenuInflater); verify(mMenu).add(Menu.NONE, MENU_TOGGLE_APPS, Menu.NONE, R.string.show_all_apps); } @Test public void testOptionsMenu_clickToggleAppsMenu_dataChanged() { testToggleAllApps(true); testToggleAllApps(false); } @Test public void testExtractKeyFromSipper_typeAPPUidObjectNull_returnPackageNames() { mNormalBatterySipper.uidObj = null; mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; final String key = mFragment.extractKeyFromSipper(mNormalBatterySipper); assertThat(key).isEqualTo(TextUtils.concat(mNormalBatterySipper.getPackages()).toString()); } @Test public void testExtractKeyFromSipper_typeOther_returnDrainType() { mNormalBatterySipper.uidObj = null; mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH; final String key = mFragment.extractKeyFromSipper(mNormalBatterySipper); assertThat(key).isEqualTo(mNormalBatterySipper.drainType.toString()); } @Test public void testExtractKeyFromSipper_typeAPPUidObjectNotNull_returnUid() { mNormalBatterySipper.uidObj = new BatteryStatsImpl.Uid(new BatteryStatsImpl(), UID); mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; final String key = mFragment.extractKeyFromSipper(mNormalBatterySipper); assertThat(key).isEqualTo(Integer.toString(mNormalBatterySipper.getUid())); } @Test public void testSetUsageSummary_timeLessThanOneMinute_DoNotSetSummary() { final long usageTimeMs = 59 * DateUtils.SECOND_IN_MILLIS; mFragment.setUsageSummary(mPreference, usageTimeMs); assertThat(mPreference.getSummary()).isNull(); } @Test public void testSetUsageSummary_timeMoreThanOneMinute_setSummary() { final long usageTimeMs = 2 * DateUtils.MINUTE_IN_MILLIS; doReturn(mRealContext.getText(R.string.battery_screen_usage)).when(mFragment).getText( R.string.battery_screen_usage); doReturn(mRealContext).when(mFragment).getContext(); final String expectedSummary = "Screen usage 2m"; mFragment.setUsageSummary(mPreference, usageTimeMs); assertThat(mPreference.getSummary().toString()).isEqualTo(expectedSummary); } @Test public void testUpdatePreference_hasRemainingTime_showRemainingLabel() { mBatteryInfo.remainingLabel = TIME_LEFT; mFragment.updateHeaderPreference(mBatteryInfo); verify(mSummary1).setText(mBatteryInfo.remainingLabel); } @Test public void testUpdatePreference_updateBatteryInfo() { mBatteryInfo.remainingLabel = TIME_LEFT; mBatteryInfo.batteryLevel = BATTERY_LEVEL; mBatteryInfo.discharging = true; mFragment.updateHeaderPreference(mBatteryInfo); assertThat(mBatteryMeterView.mDrawable.getBatteryLevel()).isEqualTo(BATTERY_LEVEL); assertThat(mBatteryMeterView.mDrawable.getCharging()).isEqualTo(false); } @Test public void testUpdatePreference_noRemainingTime_showStatusLabel() { mBatteryInfo.remainingLabel = null; mFragment.updateHeaderPreference(mBatteryInfo); verify(mSummary1).setText(mBatteryInfo.statusLabel); } @Test public void testUpdateHeaderPreference_asyncUpdate_shouldNotCrash() { when(mFragment.getContext()).thenReturn(null); mBatteryInfo.remainingTimeUs = REMAINING_TIME_US; //Should not crash mFragment.updateHeaderPreference(mBatteryInfo); } private void testToggleAllApps(final boolean isShowApps) { mFragment.mShowAllApps = isShowApps; mFragment.onOptionsItemSelected(mToggleAppsMenu); assertThat(mFragment.mShowAllApps).isEqualTo(!isShowApps); } @Test public void testFindBatterySipperByType_findTypeScreen() { BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList, BatterySipper.DrainType.SCREEN); assertThat(sipper).isSameAs(mScreenBatterySipper); } @Test public void testFindBatterySipperByType_findTypeApp() { BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList, BatterySipper.DrainType.APP); assertThat(sipper).isSameAs(mNormalBatterySipper); } @Test public void testUpdateScreenPreference_showCorrectSummary() { doReturn(mScreenBatterySipper).when(mFragment).findBatterySipperByType(any(), any()); doReturn(mRealContext).when(mFragment).getContext(); final CharSequence expectedSummary = Utils.formatElapsedTime(mRealContext, USAGE_TIME_MS, false); mFragment.updateScreenPreference(); assertThat(mScreenUsagePref.getSubtitle()).isEqualTo(expectedSummary); } @Test public void testUpdateLastFullChargePreference_showCorrectSummary() { final CharSequence formattedString = mRealContext.getText( R.string.power_last_full_charge_summary); final CharSequence timeSequence = Utils.formatElapsedTime(mRealContext, TIME_SINCE_LAST_FULL_CHARGE_MS, false); final CharSequence expectedSummary = TextUtils.expandTemplate( formattedString, timeSequence); doReturn(formattedString).when(mFragment).getText(R.string.power_last_full_charge_summary); doReturn(mRealContext).when(mFragment).getContext(); mFragment.updateLastFullChargePreference(TIME_SINCE_LAST_FULL_CHARGE_MS); assertThat(mLastFullChargePref.getSubtitle()).isEqualTo(expectedSummary); } @Test public void testUpdatePreference_usageListEmpty_shouldNotCrash() { when(mBatteryHelper.getUsageList()).thenReturn(new ArrayList()); doReturn(STUB_STRING).when(mFragment).getString(anyInt(), any()); doReturn(mRealContext).when(mFragment).getContext(); // Should not crash when update mFragment.updateScreenPreference(); } @Test public void testCalculatePercentage() { final double percent = mFragment.calculatePercentage(POWER_MAH, DISCHARGE_AMOUNT); assertThat(percent).isWithin(PRECISION).of(POWER_USAGE_PERCENTAGE); } @Test public void testNonIndexableKeys_MatchPreferenceKeys() { final Context context = RuntimeEnvironment.application; final List niks = PowerUsageSummary.SEARCH_INDEX_DATA_PROVIDER .getNonIndexableKeys(context); final List keys = XmlTestUtils.getKeysFromPreferenceXml(context, R.xml.power_usage_summary); assertThat(keys).containsAllIn(niks); } @Test public void testPreferenceControllers_getPreferenceKeys_existInPreferenceScreen() { final Context context = RuntimeEnvironment.application; final PowerUsageSummary fragment = new PowerUsageSummary(); final List preferenceScreenKeys = XmlTestUtils.getKeysFromPreferenceXml(context, fragment.getPreferenceScreenResId()); final List preferenceKeys = new ArrayList<>(); for (PreferenceController controller : fragment.getPreferenceControllers(context)) { preferenceKeys.add(controller.getPreferenceKey()); } assertThat(preferenceScreenKeys).containsAllIn(preferenceKeys); } @Test public void testUpdateAnomalySparseArray() { mFragment.mAnomalySparseArray = new SparseArray<>(); final List anomalies = new ArrayList<>(); final Anomaly anomaly1 = new Anomaly.Builder().setUid(UID).build(); final Anomaly anomaly2 = new Anomaly.Builder().setUid(UID).build(); final Anomaly anomaly3 = new Anomaly.Builder().setUid(UID_2).build(); anomalies.add(anomaly1); anomalies.add(anomaly2); anomalies.add(anomaly3); mFragment.updateAnomalySparseArray(anomalies); assertThat(mFragment.mAnomalySparseArray.get(UID)).containsExactly(anomaly1, anomaly2); assertThat(mFragment.mAnomalySparseArray.get(UID_2)).containsExactly(anomaly3); } @Test public void testBatteryPredictionLoaderCallbacks_DoesNotCrashOnNull() { // Sanity test to check for crash mFragment.mBatteryPredictionLoaderCallbacks.onLoadFinished(null, null); } @Test public void testOnCreate_BatteryPredictionSkippedWhenDisabled() { PowerUsageFeatureProvider provider = mFeatureFactory.getPowerUsageFeatureProvider(mContext); when(provider.isEnhancedBatteryPredictionEnabled(any())).thenReturn(false); mFragment.mPowerFeatureProvider = provider; doReturn(mLoaderManager).when(mFragment).getLoaderManager(); mFragment.initializeBatteryEstimateLoader(); verify(mLoaderManager, never()).initLoader(eq(PowerUsageSummary.BATTERY_ESTIMATE_LOADER), eq(Bundle.EMPTY), any()); } @Test public void testInitAnomalyDetectionIfPossible_detectionEnabled_init() { when(mFeatureFactory.powerUsageFeatureProvider.isAnomalyDetectionEnabled()).thenReturn( true); doReturn(mLoaderManager).when(mFragment).getLoaderManager(); mFragment.initAnomalyDetectionIfPossible(); verify(mLoaderManager).initLoader(eq(PowerUsageSummary.ANOMALY_LOADER), eq(Bundle.EMPTY), any()); } public static class TestFragment extends PowerUsageSummary { private Context mContext; private boolean mStartActivityCalled; private Intent mStartActivityIntent; public TestFragment(Context context) { mContext = context; } @Override public Context getContext() { return mContext; } @Override public void startActivity(Intent intent) { mStartActivityCalled = true; mStartActivityIntent = intent; } @Override protected void refreshUi() { // Leave it empty for toggle apps menu test } } }