diff --git a/res/values/strings.xml b/res/values/strings.xml index 7cd9ae4d892..b997914c059 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4285,8 +4285,17 @@ %1$s to charge - - Use since last full charge + + Screen usage + + Screen consumption + + Mobile network scanning + + + App usage since last full charge + + Device usage since last full charge Battery use since unplugged @@ -4483,6 +4492,8 @@ Used for %1$s + + %1$s of overall battery Usage breakdown since last full charge diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index b6808c000f4..58cab77aa71 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -55,4 +55,22 @@ android:key="app_list" android:title="@string/power_usage_list_summary"/> + + + + + + + + + + diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java index b067edf6b07..fe7ef6eea5a 100644 --- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java +++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java @@ -59,7 +59,7 @@ public class PowerGaugePreference extends TintablePreference { } public void setPercent(double percentOfTotal) { - mProgress = Utils.formatPercentage((int) (percentOfTotal + 0.5)); + mProgress = Utils.formatPercentage(percentOfTotal, true); notifyChanged(); } diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 44499109809..c44a3735d1f 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -78,11 +78,17 @@ public class PowerUsageSummary extends PowerUsageBase { private static final boolean USE_FAKE_DATA = false; private static final String KEY_APP_LIST = "app_list"; private static final String KEY_BATTERY_HEADER = "battery_header"; + private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5; private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10; private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10; private static final int SECONDS_IN_HOUR = 60 * 60; + private static final String KEY_SCREEN_USAGE = "screen_usage"; + private static final String KEY_SCREEN_CONSUMPTION = "screen_consumption"; + private static final String KEY_CELLULAR_NETWORK = "cellular_network"; + + private static final int MENU_STATS_TYPE = Menu.FIRST; private static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3; @VisibleForTesting @@ -93,6 +99,13 @@ public class PowerUsageSummary extends PowerUsageBase { @VisibleForTesting boolean mShowAllApps = false; + + Preference mScreenUsagePref; + @VisibleForTesting + Preference mScreenConsumptionPref; + @VisibleForTesting + Preference mCellularNetworkPref; + private LayoutPreference mBatteryLayoutPref; private PreferenceGroup mAppListGroup; @@ -105,6 +118,9 @@ public class PowerUsageSummary extends PowerUsageBase { mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER); mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); + mScreenUsagePref = findPreference(KEY_SCREEN_USAGE); + mScreenConsumptionPref = findPreference(KEY_SCREEN_CONSUMPTION); + mCellularNetworkPref = findPreference(KEY_CELLULAR_NETWORK); } @Override @@ -384,6 +400,11 @@ public class PowerUsageSummary extends PowerUsageBase { context.getTheme().resolveAttribute(android.R.attr.colorControlNormal, value, true); final int colorControl = context.getColor(value.resourceId); final String usedTime = context.getString(R.string.battery_used_for); + final int dischargeAmount = USE_FAKE_DATA ? 5000 + : stats != null ? stats.getDischargeAmount(mStatsType) : 0; + + updateScreenPreference(dischargeAmount); + updateCellularPreference(dischargeAmount); if (averagePower >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP || USE_FAKE_DATA) { final List usageList = getCoalescedUsageList( @@ -391,8 +412,6 @@ public class PowerUsageSummary extends PowerUsageBase { double hiddenPowerMah = mShowAllApps ? 0 : removeHiddenBatterySippers(usageList); - final int dischargeAmount = USE_FAKE_DATA ? 5000 - : stats != null ? stats.getDischargeAmount(mStatsType) : 0; final int numSippers = usageList.size(); for (int i = 0; i < numSippers; i++) { final BatterySipper sipper = usageList.get(i); @@ -478,6 +497,39 @@ public class PowerUsageSummary extends PowerUsageBase { BatteryEntry.startRequestQueue(); } + @VisibleForTesting + BatterySipper findBatterySipperByType(List usageList, DrainType type) { + for (int i = 0, size = usageList.size(); i < size; i++) { + final BatterySipper sipper = usageList.get(i); + if (sipper.drainType == type) { + return sipper; + } + } + return null; + } + + @VisibleForTesting + void updateScreenPreference(final int dischargeAmount) { + final BatterySipper sipper = findBatterySipperByType( + mStatsHelper.getUsageList(), DrainType.SCREEN); + final Context context = getContext(); + final double percentOfTotal = calculatePercentage(sipper.totalPowerMah, dischargeAmount); + + mScreenUsagePref.setSummary(getString(R.string.battery_used_for, + Utils.formatElapsedTime(context, sipper.usageTimeMs, false))); + mScreenConsumptionPref.setSummary(getString(R.string.battery_overall_usage, + Utils.formatPercentage(percentOfTotal, true))); + } + + @VisibleForTesting + void updateCellularPreference(final int dischargeAmount) { + final BatterySipper sipper = findBatterySipperByType( + mStatsHelper.getUsageList(), DrainType.CELL); + final double percentOfTotal = calculatePercentage(sipper.totalPowerMah, dischargeAmount); + mCellularNetworkPref.setSummary(getString(R.string.battery_overall_usage, + Utils.formatPercentage(percentOfTotal, true))); + } + @VisibleForTesting void updateHeaderPreference(BatteryInfo info) { final BatteryMeterView batteryView = (BatteryMeterView) mBatteryLayoutPref @@ -502,6 +554,11 @@ public class PowerUsageSummary extends PowerUsageBase { batteryView.setBatteryInfo(info.mBatteryLevel); } + @VisibleForTesting + double calculatePercentage(double powerUsage, double dischargeAmount) { + return ((powerUsage / mStatsHelper.getTotalPower()) * dischargeAmount); + } + @VisibleForTesting void setUsageSummary(Preference preference, String usedTimePrefix, long usageTimeMs) { // Only show summary when usage time is longer than one minute diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index dd849737cbd..72f6a87992d 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge; import android.content.Context; import android.content.Intent; import android.os.Process; +import android.support.v7.preference.Preference; import android.text.TextUtils; import android.text.format.DateUtils; import android.view.Menu; @@ -26,9 +27,12 @@ import android.view.MenuItem; import android.view.View; import android.widget.TextView; 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.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settingslib.BatteryInfo; @@ -38,9 +42,8 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.List; @@ -49,7 +52,10 @@ import static com.android.settings.fuelgauge.PowerUsageBase.MENU_STATS_REFRESH; import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADDITIONAL_BATTERY_INFO; 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.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -59,7 +65,7 @@ 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(RobolectricTestRunner.class) +@RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class PowerUsageSummaryTest { private static final String[] PACKAGE_NAMES = {"com.app1", "com.app2"}; @@ -67,9 +73,13 @@ public class PowerUsageSummaryTest { private static final int UID = 123; private static final int POWER_MAH = 100; private static final long REMAINING_TIME_US = 100000; + private static final int DISCHARGE_AMOUNT = 100; + private static final long USAGE_TIME_MS = 10000; + 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 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"); @@ -92,6 +102,8 @@ public class PowerUsageSummaryTest { @Mock private BatterySipper mSystemBatterySipper; @Mock + private BatterySipper mCellBatterySipper; + @Mock private PowerGaugePreference mPreference; @Mock private LayoutPreference mBatteryLayoutPref; @@ -105,15 +117,26 @@ public class PowerUsageSummaryTest { private TextView mSummary2; @Mock private BatteryInfo mBatteryInfo; + @Mock + private Preference mScreenUsagePref; + @Mock + private Preference mScreenConsumptionPref; + @Mock + private Preference mCellularNetworkPref; + @Mock + private BatteryStatsHelper mBatteryHelper; + private Context mRealContext; private TestFragment mFragment; private FakeFeatureFactory mFeatureFactory; private PowerUsageSummary mPowerUsageSummary; + private List mUsageList; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mRealContext = RuntimeEnvironment.application; FakeFeatureFactory.setupForTest(mContext); mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); @@ -129,13 +152,19 @@ public class PowerUsageSummaryTest { when(mToggleAppsMenu.getItemId()).thenReturn(MENU_TOGGLE_APPS); when(mFeatureFactory.powerUsageFeatureProvider.getAdditionalBatteryInfoIntent()) .thenReturn(ADDITIONAL_BATTERY_INFO_INTENT); + when(mBatteryHelper.getTotalPower()).thenReturn(TOTAL_POWER); mPowerUsageSummary = spy(new PowerUsageSummary()); - when(mPowerUsageSummary.getContext()).thenReturn(mContext); + when(mPowerUsageSummary.getContext()).thenReturn(mRealContext); 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.summary2)).thenReturn(mSummary2); when(mBatteryLayoutPref.findViewById(R.id.time)).thenReturn(mTimeText); @@ -145,9 +174,19 @@ public class PowerUsageSummaryTest { mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE; + mScreenBatterySipper.usageTimeMs = USAGE_TIME_MS; + mSystemBatterySipper.drainType = BatterySipper.DrainType.APP; mSystemBatterySipper.totalPowerMah = BATTERY_SYSTEM_USAGE; when(mSystemBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID); + + mUsageList = new ArrayList<>(); + mUsageList.add(mNormalBatterySipper); + mUsageList.add(mScreenBatterySipper); + mUsageList.add(mCellBatterySipper); + + mPowerUsageSummary.mStatsHelper = mBatteryHelper; + when(mBatteryHelper.getUsageList()).thenReturn(mUsageList); } @Test @@ -326,6 +365,7 @@ public class PowerUsageSummaryTest { verify(mSummary1).setText(R.string.estimated_time_left); } + private void testToggleAllApps(final boolean isShowApps) { mFragment.mShowAllApps = isShowApps; @@ -333,6 +373,61 @@ public class PowerUsageSummaryTest { assertThat(mFragment.mShowAllApps).isEqualTo(!isShowApps); } + @Test + public void testFindBatterySipperByType_findTypeScreen() { + BatterySipper sipper = mPowerUsageSummary.findBatterySipperByType(mUsageList, + BatterySipper.DrainType.SCREEN); + + assertThat(sipper).isSameAs(mScreenBatterySipper); + } + + @Test + public void testFindBatterySipperByType_findTypeApp() { + BatterySipper sipper = mPowerUsageSummary.findBatterySipperByType(mUsageList, + BatterySipper.DrainType.APP); + + assertThat(sipper).isSameAs(mNormalBatterySipper); + } + + @Test + public void testUpdateCellularPreference_ShowCorrectSummary() { + mPowerUsageSummary.mCellularNetworkPref = mCellularNetworkPref; + final double percent = POWER_MAH / TOTAL_POWER * DISCHARGE_AMOUNT; + final String expectedSummary = mRealContext.getString(R.string.battery_overall_usage, + Utils.formatPercentage((int) percent)); + doReturn(expectedSummary).when(mPowerUsageSummary) + .getString(eq(R.string.battery_overall_usage), anyInt()); + mPowerUsageSummary.updateCellularPreference(DISCHARGE_AMOUNT); + + verify(mCellularNetworkPref).setSummary(expectedSummary); + } + + @Test + public void testUpdateScreenPreference_ShowCorrectSummary() { + mPowerUsageSummary.mScreenUsagePref = mScreenUsagePref; + mPowerUsageSummary.mScreenConsumptionPref = mScreenConsumptionPref; + final String expectedUsedTime = mRealContext.getString(R.string.battery_used_for, + Utils.formatElapsedTime(mRealContext, USAGE_TIME_MS, false)); + final double percent = BATTERY_SCREEN_USAGE / TOTAL_POWER * DISCHARGE_AMOUNT; + final String expectedOverallUsage = mRealContext.getString(R.string.battery_overall_usage, + Utils.formatPercentage((int) percent)); + doReturn(expectedUsedTime).when(mPowerUsageSummary).getString( + eq(R.string.battery_used_for), anyInt()); + doReturn(expectedOverallUsage).when(mPowerUsageSummary).getString( + eq(R.string.battery_overall_usage), anyInt()); + + mPowerUsageSummary.updateScreenPreference(DISCHARGE_AMOUNT); + + verify(mScreenUsagePref).setSummary(expectedUsedTime); + verify(mScreenConsumptionPref).setSummary(expectedOverallUsage); + } + + @Test + public void testCalculatePercentage() { + final double percent = mPowerUsageSummary.calculatePercentage(POWER_MAH, DISCHARGE_AMOUNT); + assertThat(percent).isWithin(PRECISION).of(POWER_USAGE_PERCENTAGE); + } + public static class TestFragment extends PowerUsageSummary { private Context mContext;