diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 86cb203dc97..8e730c03c04 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -22,7 +22,9 @@ import android.os.SystemClock; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import android.text.format.DateUtils; import android.util.Log; +import android.util.SparseLongArray; import com.android.internal.os.BatterySipper; import com.android.settings.overlay.FeatureFactory; @@ -115,25 +117,64 @@ public class BatteryUtils { } /** - * Remove the {@link BatterySipper} that we should hide. + * Remove the {@link BatterySipper} that we should hide and smear the screen usage based on + * foreground activity time. * * @param sippers sipper list that need to check and remove * @return the total power of the hidden items of {@link BatterySipper} + * for proportional smearing */ public double removeHiddenBatterySippers(List sippers) { - double totalPowerMah = 0; + double proportionalSmearPowerMah = 0; + BatterySipper screenSipper = null; for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper sipper = sippers.get(i); if (shouldHideSipper(sipper)) { sippers.remove(i); - if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED) { - // Don't add it if it is overcounted - totalPowerMah += sipper.totalPowerMah; + if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED + && sipper.drainType != BatterySipper.DrainType.SCREEN + && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED) { + // Don't add it if it is overcounted, unaccounted or screen + proportionalSmearPowerMah += sipper.totalPowerMah; } } + + if (sipper.drainType == BatterySipper.DrainType.SCREEN) { + screenSipper = sipper; + } } - return totalPowerMah; + smearScreenBatterySipper(sippers, screenSipper); + + return proportionalSmearPowerMah; + } + + /** + * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity + * time. + */ + @VisibleForTesting + void smearScreenBatterySipper(List sippers, BatterySipper screenSipper) { + final long rawRealtimeMs = SystemClock.elapsedRealtime(); + long totalActivityTimeMs = 0; + final SparseLongArray activityTimeArray = new SparseLongArray(); + for (int i = 0, size = sippers.size(); i < size; i++) { + final BatteryStats.Uid uid = sippers.get(i).uidObj; + if (uid != null) { + final long timeMs = getForegroundActivityTotalTimeMs(uid, rawRealtimeMs); + activityTimeArray.put(uid.getUid(), timeMs); + totalActivityTimeMs += timeMs; + } + } + + if (totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) { + final double screenPowerMah = screenSipper.totalPowerMah; + for (int i = 0, size = sippers.size(); i < size; i++) { + final BatterySipper sipper = sippers.get(i); + sipper.totalPowerMah += screenPowerMah * activityTimeArray.get(sipper.getUid(), 0) + / totalActivityTimeMs; + } + } } /** @@ -186,5 +227,15 @@ public class BatteryUtils { return mPackageManager == null; } + @VisibleForTesting + long getForegroundActivityTotalTimeMs(BatteryStats.Uid uid, long rawRealtimeMs) { + final BatteryStats.Timer timer = uid.getForegroundActivityTimer(); + if (timer != null) { + return timer.getTotalTimeLocked(rawRealtimeMs, BatteryStats.STATS_SINCE_CHARGED); + } + + return 0; + } + } diff --git a/src/com/android/settings/fuelgauge/PowerUsageBase.java b/src/com/android/settings/fuelgauge/PowerUsageBase.java index aee55d6810d..060abddb185 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageBase.java +++ b/src/com/android/settings/fuelgauge/PowerUsageBase.java @@ -57,7 +57,7 @@ public abstract class PowerUsageBase extends DashboardFragment mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(getContext()); mBatteryBroadcastReceiver.setBatteryChangedListener(() -> { - getLoaderManager().restartLoader(0, null, this); + restartBatteryStatsLoader(); }); getLoaderManager().initLoader(0, icicle, this); @@ -95,6 +95,10 @@ public abstract class PowerUsageBase extends DashboardFragment } } + protected void restartBatteryStatsLoader() { + getLoaderManager().restartLoader(0, Bundle.EMPTY, this); + } + protected abstract void refreshUi(); protected void updatePreference(BatteryHistoryPreference historyPref) { diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 23895e3df65..bb1482791b5 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -377,7 +377,7 @@ public class PowerUsageSummary extends PowerUsageBase implements item.setTitle(mShowAllApps ? R.string.hide_extra_apps : R.string.show_all_apps); metricsFeatureProvider.action(context, MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE, mShowAllApps); - refreshUi(); + restartBatteryStatsLoader(); return true; default: return super.onOptionsItemSelected(item); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java index fd895583162..f6a8587f516 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge; import android.content.Context; import android.os.BatteryStats; import android.os.Process; +import android.text.format.DateUtils; import com.android.internal.os.BatterySipper; import com.android.settings.SettingsRobolectricTestRunner; @@ -47,8 +48,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.spy; @@ -62,6 +66,8 @@ public class BatteryUtilsTest { private static final long TIME_STATE_TOP_SLEEPING = 2500 * UNIT; private static final long TIME_STATE_FOREGROUND = 3000 * UNIT; private static final long TIME_STATE_BACKGROUND = 6000 * UNIT; + private static final long TIME_FOREGROUND_ACTIVITY_ZERO = 0; + private static final long TIME_FOREGROUND_ACTIVITY = 100 * DateUtils.MINUTE_IN_MILLIS; private static final int UID = 123; private static final long TIME_EXPECTED_FOREGROUND = 1500; @@ -71,6 +77,7 @@ public class BatteryUtilsTest { private static final double BATTERY_SYSTEM_USAGE = 600; private static final double BATTERY_OVERACCOUNTED_USAGE = 500; private static final double BATTERY_UNACCOUNTED_USAGE = 700; + private static final double BATTERY_APP_USAGE = 100; private static final double TOTAL_BATTERY_USAGE = 1000; private static final double HIDDEN_USAGE = 200; private static final int DISCHARGE_AMOUNT = 80; @@ -180,11 +187,12 @@ public class BatteryUtilsTest { sippers.add(mUnaccountedBatterySipper); when(mProvider.isTypeSystem(mSystemBatterySipper)) .thenReturn(true); + doNothing().when(mBatteryUtils).smearScreenBatterySipper(any(), any()); final double totalUsage = mBatteryUtils.removeHiddenBatterySippers(sippers); + assertThat(sippers).containsExactly(mNormalBatterySipper); - assertThat(totalUsage).isWithin(PRECISION).of( - BATTERY_SCREEN_USAGE + BATTERY_SYSTEM_USAGE + BATTERY_UNACCOUNTED_USAGE); + assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_SYSTEM_USAGE); } @Test @@ -259,4 +267,43 @@ public class BatteryUtilsTest { HIDDEN_USAGE, DISCHARGE_AMOUNT)) .isWithin(PRECISION).of(PERCENT_SYSTEM_USAGE); } + + @Test + public void testSmearScreenBatterySipper() { + final BatterySipper sipperNull = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO, + BATTERY_APP_USAGE, 0 /* uid */, true /* isUidNull */); + final BatterySipper sipperBg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO, + BATTERY_APP_USAGE, 1 /* uid */, false /* isUidNull */); + final BatterySipper sipperFg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY, + BATTERY_APP_USAGE, 2 /* uid */, false /* isUidNull */); + + final List sippers = new ArrayList<>(); + sippers.add(sipperNull); + sippers.add(sipperBg); + sippers.add(sipperFg); + + mBatteryUtils.smearScreenBatterySipper(sippers, mScreenBatterySipper); + + assertThat(sipperNull.totalPowerMah).isWithin(PRECISION).of(BATTERY_APP_USAGE); + assertThat(sipperBg.totalPowerMah).isWithin(PRECISION).of(BATTERY_APP_USAGE); + assertThat(sipperFg.totalPowerMah).isWithin(PRECISION).of( + BATTERY_APP_USAGE + BATTERY_SCREEN_USAGE); + } + + private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah, + int uidCode, boolean isUidNull) { + final BatterySipper sipper = mock(BatterySipper.class); + sipper.drainType = BatterySipper.DrainType.APP; + sipper.totalPowerMah = totalPowerMah; + doReturn(uidCode).when(sipper).getUid(); + if (!isUidNull) { + final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS); + doReturn(activityTime).when(mBatteryUtils).getForegroundActivityTotalTimeMs(eq(uid), + anyLong()); + doReturn(uidCode).when(uid).getUid(); + sipper.uidObj = uid; + } + + return sipper; + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index c068d118c54..e68b27ca5c0 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -69,6 +69,7 @@ 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; @@ -172,6 +173,7 @@ public class PowerUsageSummaryTest { mFragment.initFeatureProvider(); mBatteryMeterView = spy(new BatteryMeterView(mRealContext)); mBatteryMeterView.mDrawable = new BatteryMeterView.BatteryMeterDrawable(mRealContext, 0); + doNothing().when(mFragment).restartBatteryStatsLoader(); when(mFragment.getActivity()).thenReturn(mSettingsActivity); when(mAdditionalBatteryInfoMenu.getItemId())