Loading src/com/android/settings/applications/InstalledAppDetails.java +10 −3 Original line number Diff line number Diff line Loading @@ -91,6 +91,7 @@ import com.android.settings.datausage.DataUsageList; import com.android.settings.datausage.DataUsageSummary; import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; import com.android.settings.fuelgauge.BatteryEntry; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.notification.AppNotificationSettings; import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.NotificationBackend.AppRow; Loading Loading @@ -197,6 +198,7 @@ public class InstalledAppDetails extends AppInfoBase private AppStorageStats mLastResult; private String mBatteryPercent; private BatteryUtils mBatteryUtils; private boolean handleDisableable(Button button) { boolean disableable = false; Loading Loading @@ -357,6 +359,7 @@ public class InstalledAppDetails extends AppInfoBase removePreference(KEY_DATA); } mBatteryHelper = new BatteryStatsHelper(getActivity(), true); mBatteryUtils = BatteryUtils.getInstance(getContext()); } @Override Loading Loading @@ -685,10 +688,14 @@ public class InstalledAppDetails extends AppInfoBase private void updateBattery() { if (mSipper != null) { mBatteryPreference.setEnabled(true); int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount( final int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount( BatteryStats.STATS_SINCE_CHARGED); final int percentOfMax = (int) ((mSipper.totalPowerMah) / mBatteryHelper.getTotalPower() * dischargeAmount + .5f); final List<BatterySipper> usageList = new ArrayList<>(mBatteryHelper.getUsageList()); final double hiddenAmount = mBatteryUtils.removeHiddenBatterySippers(usageList); final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent( mSipper.totalPowerMah, mBatteryHelper.getTotalPower(), hiddenAmount, dischargeAmount); mBatteryPercent = Utils.formatPercentage(percentOfMax); mBatteryPreference.setSummary(getString(R.string.battery_summary, mBatteryPercent)); } else { Loading src/com/android/settings/fuelgauge/BatteryUtils.java +73 −0 Original line number Diff line number Diff line Loading @@ -22,10 +22,15 @@ import android.os.BatteryStats; import android.os.SystemClock; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.format.DateUtils; import android.util.Log; import com.android.internal.os.BatterySipper; import com.android.settings.overlay.FeatureFactory; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; /** * Utils for battery operation Loading @@ -43,9 +48,14 @@ public class BatteryUtils { } private static final String TAG = "BatteryUtils"; private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5; private static final int SECONDS_IN_HOUR = 60 * 60; private static BatteryUtils sInstance; private PackageManager mPackageManager; @VisibleForTesting PowerUsageFeatureProvider mPowerUsageFeatureProvider; public static BatteryUtils getInstance(Context context) { if (sInstance == null || sInstance.isDataCorrupted()) { Loading @@ -56,6 +66,8 @@ public class BatteryUtils { private BatteryUtils(Context context) { mPackageManager = context.getPackageManager(); mPowerUsageFeatureProvider = FeatureFactory.getFactory( context).getPowerUsageFeatureProvider(context); } public long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid, Loading Loading @@ -105,6 +117,67 @@ public class BatteryUtils { return convertUsToMs(timeUs); } /** * Remove the {@link BatterySipper} that we should hide. * * @param sippers sipper list that need to check and remove * @return the total power of the hidden items of {@link BatterySipper} */ public double removeHiddenBatterySippers(List<BatterySipper> sippers) { double totalPowerMah = 0; 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 && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED) { // Don't add it if it is overcounted or unaccounted totalPowerMah += sipper.totalPowerMah; } } } return totalPowerMah; } /** * Check whether we should hide the battery sipper. */ public boolean shouldHideSipper(BatterySipper sipper) { final BatterySipper.DrainType drainType = sipper.drainType; return drainType == BatterySipper.DrainType.IDLE || drainType == BatterySipper.DrainType.CELL || drainType == BatterySipper.DrainType.WIFI || drainType == BatterySipper.DrainType.SCREEN || drainType == BatterySipper.DrainType.BLUETOOTH || drainType == BatterySipper.DrainType.UNACCOUNTED || drainType == BatterySipper.DrainType.OVERCOUNTED || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP || mPowerUsageFeatureProvider.isTypeService(sipper) || mPowerUsageFeatureProvider.isTypeSystem(sipper); } /** * Calculate the power usage percentage for an app * * @param powerUsageMah power used by the app * @param totalPowerMah total power used in the system * @param hiddenPowerMah power used by no-actionable app that we want to hide, i.e. Screen, * Android OS. * @param dischargeAmount The discharge amount calculated by {@link BatteryStats} * @return A percentage value scaled by {@paramref dischargeAmount} * @see BatteryStats#getDischargeAmount(int) */ public double calculateBatteryPercent(double powerUsageMah, double totalPowerMah, double hiddenPowerMah, int dischargeAmount) { if (totalPowerMah == 0) { return 0; } return (powerUsageMah / (totalPowerMah - hiddenPowerMah)) * dischargeAmount; } private long convertUsToMs(long timeUs) { return timeUs / 1000; } Loading src/com/android/settings/fuelgauge/PowerUsageSummary.java +5 −46 Original line number Diff line number Diff line Loading @@ -80,11 +80,8 @@ 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_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge"; Loading Loading @@ -432,20 +429,16 @@ public class PowerUsageSummary extends PowerUsageBase { final List<BatterySipper> usageList = getCoalescedUsageList( USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList()); double hiddenPowerMah = mShowAllApps ? 0 : removeHiddenBatterySippers(usageList); double hiddenPowerMah = mShowAllApps ? 0 : mBatteryUtils.removeHiddenBatterySippers(usageList); final int numSippers = usageList.size(); for (int i = 0; i < numSippers; i++) { final BatterySipper sipper = usageList.get(i); // Deduct the power of hidden items from total power, which is used to // calculate percentOfTotal double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower() - hiddenPowerMah; double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower(); // With deduction in totalPower, percentOfTotal is higher because it adds the part // used in screen, system, etc final double percentOfTotal = totalPower == 0 ? 0 : ((sipper.totalPowerMah / totalPower) * dischargeAmount); final double percentOfTotal = mBatteryUtils.calculateBatteryPercent( sipper.totalPowerMah, totalPower, hiddenPowerMah, dischargeAmount); if (((int) (percentOfTotal + .5)) < 1) { continue; Loading Loading @@ -594,22 +587,6 @@ public class PowerUsageSummary extends PowerUsageBase { } } @VisibleForTesting boolean shouldHideSipper(BatterySipper sipper) { final DrainType drainType = sipper.drainType; return drainType == DrainType.IDLE || drainType == DrainType.CELL || drainType == DrainType.WIFI || drainType == DrainType.SCREEN || drainType == DrainType.BLUETOOTH || drainType == DrainType.UNACCOUNTED || drainType == DrainType.OVERCOUNTED || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP || mPowerFeatureProvider.isTypeService(sipper) || mPowerFeatureProvider.isTypeSystem(sipper); } @VisibleForTesting String extractKeyFromSipper(BatterySipper sipper) { if (sipper.uidObj != null) { Loading @@ -624,24 +601,6 @@ public class PowerUsageSummary extends PowerUsageBase { } } @VisibleForTesting double removeHiddenBatterySippers(List<BatterySipper> sippers) { double totalPowerMah = 0; for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper sipper = sippers.get(i); if (shouldHideSipper(sipper)) { sippers.remove(i); if (sipper.drainType != DrainType.OVERCOUNTED && sipper.drainType != DrainType.UNACCOUNTED) { // Don't add it if it is overcounted or unaccounted totalPowerMah += sipper.totalPowerMah; } } } return totalPowerMah; } @VisibleForTesting void setBatteryLayoutPreference(LayoutPreference layoutPreference) { mBatteryLayoutPref = layoutPreference; Loading tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +139 −0 Original line number Diff line number Diff line Loading @@ -15,14 +15,19 @@ */ package com.android.settings.fuelgauge; import android.content.Context; import android.os.BatteryStats; import android.os.Process; import com.android.internal.os.BatterySipper; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.testutils.FakeFeatureFactory; 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.RuntimeEnvironment; Loading @@ -36,10 +41,15 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING; 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.doReturn; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) Loading @@ -52,18 +62,45 @@ public class BatteryUtilsTest { private static final long TIME_STATE_FOREGROUND = 3000 * UNIT; private static final long TIME_STATE_BACKGROUND = 6000 * UNIT; private static final int UID = 123; private static final long TIME_EXPECTED_FOREGROUND = 9000; private static final long TIME_EXPECTED_BACKGROUND = 6000; private static final long TIME_EXPECTED_ALL = 15000; private static final double BATTERY_SCREEN_USAGE = 300; private static final double BATTERY_SYSTEM_USAGE = 600; private static final double BATTERY_OVERACCOUNTED_USAGE = 500; private static final double TOTAL_BATTERY_USAGE = 1000; private static final double HIDDEN_USAGE = 200; private static final int DISCHARGE_AMOUNT = 80; private static final double PERCENT_SYSTEM_USAGE = 60; private static final double PRECISION = 0.001; @Mock private BatteryStats.Uid mUid; @Mock private BatterySipper mNormalBatterySipper; @Mock private BatterySipper mScreenBatterySipper; @Mock private BatterySipper mOvercountedBatterySipper; @Mock private BatterySipper mSystemBatterySipper; @Mock private BatterySipper mCellBatterySipper; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; private BatteryUtils mBatteryUtils; private FakeFeatureFactory mFeatureFactory; private PowerUsageFeatureProvider mProvider; @Before public void setUp() { MockitoAnnotations.initMocks(this); FakeFeatureFactory.setupForTest(mContext); mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); mProvider = mFeatureFactory.powerUsageFeatureProvider; doReturn(TIME_STATE_TOP).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP), anyLong(), anyInt()); doReturn(TIME_STATE_FOREGROUND_SERVICE).when(mUid).getProcessStateTime( Loading @@ -75,7 +112,21 @@ public class BatteryUtilsTest { doReturn(TIME_STATE_BACKGROUND).when(mUid).getProcessStateTime(eq(PROCESS_STATE_BACKGROUND), anyLong(), anyInt()); mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE; mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE; mSystemBatterySipper.drainType = BatterySipper.DrainType.APP; mSystemBatterySipper.totalPowerMah = BATTERY_SYSTEM_USAGE; when(mSystemBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID); mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERACCOUNTED_USAGE; mBatteryUtils = BatteryUtils.getInstance(RuntimeEnvironment.application); mBatteryUtils.mPowerUsageFeatureProvider = mProvider; } @Test Loading Loading @@ -109,4 +160,92 @@ public class BatteryUtilsTest { assertThat(time).isEqualTo(0); } @Test public void testRemoveHiddenBatterySippers_ContainsHiddenSippers_RemoveAndReturnValue() { final List<BatterySipper> sippers = new ArrayList<>(); sippers.add(mNormalBatterySipper); sippers.add(mScreenBatterySipper); sippers.add(mSystemBatterySipper); sippers.add(mOvercountedBatterySipper); when(mProvider.isTypeSystem(mSystemBatterySipper)) .thenReturn(true); final double totalUsage = mBatteryUtils.removeHiddenBatterySippers(sippers); assertThat(sippers).containsExactly(mNormalBatterySipper); assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE + BATTERY_SYSTEM_USAGE); } @Test public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeOverAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeIdle_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeWifi_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.WIFI; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeCell_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeScreen_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeBluetooth_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeSystem_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID); when(mProvider.isTypeSystem(any())).thenReturn(true); assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_UidNormal_ReturnFalse() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isFalse(); } @Test public void testShouldHideSipper_TypeService_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); when(mProvider.isTypeService(any())).thenReturn(true); assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testCalculateBatteryPercent() { assertThat(mBatteryUtils.calculateBatteryPercent(BATTERY_SYSTEM_USAGE, TOTAL_BATTERY_USAGE, HIDDEN_USAGE, DISCHARGE_AMOUNT)) .isWithin(PRECISION).of(PERCENT_SYSTEM_USAGE); } } tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +1 −96 Original line number Diff line number Diff line Loading @@ -113,10 +113,6 @@ public class PowerUsageSummaryTest { @Mock private BatterySipper mScreenBatterySipper; @Mock private BatterySipper mOvercountedBatterySipper; @Mock private BatterySipper mSystemBatterySipper; @Mock private BatterySipper mCellBatterySipper; @Mock private PowerGaugePreference mPreference; Loading Loading @@ -184,16 +180,8 @@ public class PowerUsageSummaryTest { mFragment.setBatteryLayoutPreference(mBatteryLayoutPref); 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); mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERCOUNTED_USAGE; mUsageList = new ArrayList<>(); mUsageList.add(mNormalBatterySipper); mUsageList.add(mScreenBatterySipper); Loading Loading @@ -304,90 +292,7 @@ public class PowerUsageSummaryTest { } @Test public void testRemoveHiddenBatterySippers_containsHiddenSippers_removeAndReturnValue() { final List<BatterySipper> sippers = new ArrayList<>(); sippers.add(mNormalBatterySipper); sippers.add(mScreenBatterySipper); sippers.add(mSystemBatterySipper); sippers.add(mOvercountedBatterySipper); when(mFeatureFactory.powerUsageFeatureProvider.isTypeSystem(mSystemBatterySipper)) .thenReturn(true); final double totalUsage = mFragment.removeHiddenBatterySippers(sippers); assertThat(sippers).containsExactly(mNormalBatterySipper); assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE + BATTERY_SYSTEM_USAGE); } @Test public void testShouldHideSipper_typeIdle_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeOverCounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeWifi_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.WIFI; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeCell_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeScreen_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeBluetooth_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeSystem_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID); when(mFeatureFactory.powerUsageFeatureProvider.isTypeSystem(Matchers.<BatterySipper>any())) .thenReturn(true); assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_uidNormal_returnFalse() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isFalse(); } @Test public void testShouldHideSipper_typeService_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); when(mFeatureFactory.powerUsageFeatureProvider.isTypeService(Matchers.<BatterySipper>any())) .thenReturn(true); assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testSetUsageSummary_timeLessThanOneMinute_doNotSetSummary() { public void testSetUsageSummary_timeLessThanOneMinute_DoNotSetSummary() { final long usageTimeMs = 59 * DateUtils.SECOND_IN_MILLIS; mFragment.setUsageSummary(mPreference, "", usageTimeMs); Loading Loading
src/com/android/settings/applications/InstalledAppDetails.java +10 −3 Original line number Diff line number Diff line Loading @@ -91,6 +91,7 @@ import com.android.settings.datausage.DataUsageList; import com.android.settings.datausage.DataUsageSummary; import com.android.settings.fuelgauge.AdvancedPowerUsageDetail; import com.android.settings.fuelgauge.BatteryEntry; import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.notification.AppNotificationSettings; import com.android.settings.notification.NotificationBackend; import com.android.settings.notification.NotificationBackend.AppRow; Loading Loading @@ -197,6 +198,7 @@ public class InstalledAppDetails extends AppInfoBase private AppStorageStats mLastResult; private String mBatteryPercent; private BatteryUtils mBatteryUtils; private boolean handleDisableable(Button button) { boolean disableable = false; Loading Loading @@ -357,6 +359,7 @@ public class InstalledAppDetails extends AppInfoBase removePreference(KEY_DATA); } mBatteryHelper = new BatteryStatsHelper(getActivity(), true); mBatteryUtils = BatteryUtils.getInstance(getContext()); } @Override Loading Loading @@ -685,10 +688,14 @@ public class InstalledAppDetails extends AppInfoBase private void updateBattery() { if (mSipper != null) { mBatteryPreference.setEnabled(true); int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount( final int dischargeAmount = mBatteryHelper.getStats().getDischargeAmount( BatteryStats.STATS_SINCE_CHARGED); final int percentOfMax = (int) ((mSipper.totalPowerMah) / mBatteryHelper.getTotalPower() * dischargeAmount + .5f); final List<BatterySipper> usageList = new ArrayList<>(mBatteryHelper.getUsageList()); final double hiddenAmount = mBatteryUtils.removeHiddenBatterySippers(usageList); final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent( mSipper.totalPowerMah, mBatteryHelper.getTotalPower(), hiddenAmount, dischargeAmount); mBatteryPercent = Utils.formatPercentage(percentOfMax); mBatteryPreference.setSummary(getString(R.string.battery_summary, mBatteryPercent)); } else { Loading
src/com/android/settings/fuelgauge/BatteryUtils.java +73 −0 Original line number Diff line number Diff line Loading @@ -22,10 +22,15 @@ import android.os.BatteryStats; import android.os.SystemClock; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.format.DateUtils; import android.util.Log; import com.android.internal.os.BatterySipper; import com.android.settings.overlay.FeatureFactory; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; /** * Utils for battery operation Loading @@ -43,9 +48,14 @@ public class BatteryUtils { } private static final String TAG = "BatteryUtils"; private static final int MIN_POWER_THRESHOLD_MILLI_AMP = 5; private static final int SECONDS_IN_HOUR = 60 * 60; private static BatteryUtils sInstance; private PackageManager mPackageManager; @VisibleForTesting PowerUsageFeatureProvider mPowerUsageFeatureProvider; public static BatteryUtils getInstance(Context context) { if (sInstance == null || sInstance.isDataCorrupted()) { Loading @@ -56,6 +66,8 @@ public class BatteryUtils { private BatteryUtils(Context context) { mPackageManager = context.getPackageManager(); mPowerUsageFeatureProvider = FeatureFactory.getFactory( context).getPowerUsageFeatureProvider(context); } public long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid, Loading Loading @@ -105,6 +117,67 @@ public class BatteryUtils { return convertUsToMs(timeUs); } /** * Remove the {@link BatterySipper} that we should hide. * * @param sippers sipper list that need to check and remove * @return the total power of the hidden items of {@link BatterySipper} */ public double removeHiddenBatterySippers(List<BatterySipper> sippers) { double totalPowerMah = 0; 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 && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED) { // Don't add it if it is overcounted or unaccounted totalPowerMah += sipper.totalPowerMah; } } } return totalPowerMah; } /** * Check whether we should hide the battery sipper. */ public boolean shouldHideSipper(BatterySipper sipper) { final BatterySipper.DrainType drainType = sipper.drainType; return drainType == BatterySipper.DrainType.IDLE || drainType == BatterySipper.DrainType.CELL || drainType == BatterySipper.DrainType.WIFI || drainType == BatterySipper.DrainType.SCREEN || drainType == BatterySipper.DrainType.BLUETOOTH || drainType == BatterySipper.DrainType.UNACCOUNTED || drainType == BatterySipper.DrainType.OVERCOUNTED || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP || mPowerUsageFeatureProvider.isTypeService(sipper) || mPowerUsageFeatureProvider.isTypeSystem(sipper); } /** * Calculate the power usage percentage for an app * * @param powerUsageMah power used by the app * @param totalPowerMah total power used in the system * @param hiddenPowerMah power used by no-actionable app that we want to hide, i.e. Screen, * Android OS. * @param dischargeAmount The discharge amount calculated by {@link BatteryStats} * @return A percentage value scaled by {@paramref dischargeAmount} * @see BatteryStats#getDischargeAmount(int) */ public double calculateBatteryPercent(double powerUsageMah, double totalPowerMah, double hiddenPowerMah, int dischargeAmount) { if (totalPowerMah == 0) { return 0; } return (powerUsageMah / (totalPowerMah - hiddenPowerMah)) * dischargeAmount; } private long convertUsToMs(long timeUs) { return timeUs / 1000; } Loading
src/com/android/settings/fuelgauge/PowerUsageSummary.java +5 −46 Original line number Diff line number Diff line Loading @@ -80,11 +80,8 @@ 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_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge"; Loading Loading @@ -432,20 +429,16 @@ public class PowerUsageSummary extends PowerUsageBase { final List<BatterySipper> usageList = getCoalescedUsageList( USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList()); double hiddenPowerMah = mShowAllApps ? 0 : removeHiddenBatterySippers(usageList); double hiddenPowerMah = mShowAllApps ? 0 : mBatteryUtils.removeHiddenBatterySippers(usageList); final int numSippers = usageList.size(); for (int i = 0; i < numSippers; i++) { final BatterySipper sipper = usageList.get(i); // Deduct the power of hidden items from total power, which is used to // calculate percentOfTotal double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower() - hiddenPowerMah; double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower(); // With deduction in totalPower, percentOfTotal is higher because it adds the part // used in screen, system, etc final double percentOfTotal = totalPower == 0 ? 0 : ((sipper.totalPowerMah / totalPower) * dischargeAmount); final double percentOfTotal = mBatteryUtils.calculateBatteryPercent( sipper.totalPowerMah, totalPower, hiddenPowerMah, dischargeAmount); if (((int) (percentOfTotal + .5)) < 1) { continue; Loading Loading @@ -594,22 +587,6 @@ public class PowerUsageSummary extends PowerUsageBase { } } @VisibleForTesting boolean shouldHideSipper(BatterySipper sipper) { final DrainType drainType = sipper.drainType; return drainType == DrainType.IDLE || drainType == DrainType.CELL || drainType == DrainType.WIFI || drainType == DrainType.SCREEN || drainType == DrainType.BLUETOOTH || drainType == DrainType.UNACCOUNTED || drainType == DrainType.OVERCOUNTED || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP || mPowerFeatureProvider.isTypeService(sipper) || mPowerFeatureProvider.isTypeSystem(sipper); } @VisibleForTesting String extractKeyFromSipper(BatterySipper sipper) { if (sipper.uidObj != null) { Loading @@ -624,24 +601,6 @@ public class PowerUsageSummary extends PowerUsageBase { } } @VisibleForTesting double removeHiddenBatterySippers(List<BatterySipper> sippers) { double totalPowerMah = 0; for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper sipper = sippers.get(i); if (shouldHideSipper(sipper)) { sippers.remove(i); if (sipper.drainType != DrainType.OVERCOUNTED && sipper.drainType != DrainType.UNACCOUNTED) { // Don't add it if it is overcounted or unaccounted totalPowerMah += sipper.totalPowerMah; } } } return totalPowerMah; } @VisibleForTesting void setBatteryLayoutPreference(LayoutPreference layoutPreference) { mBatteryLayoutPref = layoutPreference; Loading
tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +139 −0 Original line number Diff line number Diff line Loading @@ -15,14 +15,19 @@ */ package com.android.settings.fuelgauge; import android.content.Context; import android.os.BatteryStats; import android.os.Process; import com.android.internal.os.BatterySipper; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.testutils.FakeFeatureFactory; 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.RuntimeEnvironment; Loading @@ -36,10 +41,15 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING; 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.doReturn; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) Loading @@ -52,18 +62,45 @@ public class BatteryUtilsTest { private static final long TIME_STATE_FOREGROUND = 3000 * UNIT; private static final long TIME_STATE_BACKGROUND = 6000 * UNIT; private static final int UID = 123; private static final long TIME_EXPECTED_FOREGROUND = 9000; private static final long TIME_EXPECTED_BACKGROUND = 6000; private static final long TIME_EXPECTED_ALL = 15000; private static final double BATTERY_SCREEN_USAGE = 300; private static final double BATTERY_SYSTEM_USAGE = 600; private static final double BATTERY_OVERACCOUNTED_USAGE = 500; private static final double TOTAL_BATTERY_USAGE = 1000; private static final double HIDDEN_USAGE = 200; private static final int DISCHARGE_AMOUNT = 80; private static final double PERCENT_SYSTEM_USAGE = 60; private static final double PRECISION = 0.001; @Mock private BatteryStats.Uid mUid; @Mock private BatterySipper mNormalBatterySipper; @Mock private BatterySipper mScreenBatterySipper; @Mock private BatterySipper mOvercountedBatterySipper; @Mock private BatterySipper mSystemBatterySipper; @Mock private BatterySipper mCellBatterySipper; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; private BatteryUtils mBatteryUtils; private FakeFeatureFactory mFeatureFactory; private PowerUsageFeatureProvider mProvider; @Before public void setUp() { MockitoAnnotations.initMocks(this); FakeFeatureFactory.setupForTest(mContext); mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); mProvider = mFeatureFactory.powerUsageFeatureProvider; doReturn(TIME_STATE_TOP).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP), anyLong(), anyInt()); doReturn(TIME_STATE_FOREGROUND_SERVICE).when(mUid).getProcessStateTime( Loading @@ -75,7 +112,21 @@ public class BatteryUtilsTest { doReturn(TIME_STATE_BACKGROUND).when(mUid).getProcessStateTime(eq(PROCESS_STATE_BACKGROUND), anyLong(), anyInt()); mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE; mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE; mSystemBatterySipper.drainType = BatterySipper.DrainType.APP; mSystemBatterySipper.totalPowerMah = BATTERY_SYSTEM_USAGE; when(mSystemBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID); mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERACCOUNTED_USAGE; mBatteryUtils = BatteryUtils.getInstance(RuntimeEnvironment.application); mBatteryUtils.mPowerUsageFeatureProvider = mProvider; } @Test Loading Loading @@ -109,4 +160,92 @@ public class BatteryUtilsTest { assertThat(time).isEqualTo(0); } @Test public void testRemoveHiddenBatterySippers_ContainsHiddenSippers_RemoveAndReturnValue() { final List<BatterySipper> sippers = new ArrayList<>(); sippers.add(mNormalBatterySipper); sippers.add(mScreenBatterySipper); sippers.add(mSystemBatterySipper); sippers.add(mOvercountedBatterySipper); when(mProvider.isTypeSystem(mSystemBatterySipper)) .thenReturn(true); final double totalUsage = mBatteryUtils.removeHiddenBatterySippers(sippers); assertThat(sippers).containsExactly(mNormalBatterySipper); assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE + BATTERY_SYSTEM_USAGE); } @Test public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeOverAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeIdle_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeWifi_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.WIFI; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeCell_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeScreen_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeBluetooth_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH; assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeSystem_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID); when(mProvider.isTypeSystem(any())).thenReturn(true); assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_UidNormal_ReturnFalse() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isFalse(); } @Test public void testShouldHideSipper_TypeService_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); when(mProvider.isTypeService(any())).thenReturn(true); assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testCalculateBatteryPercent() { assertThat(mBatteryUtils.calculateBatteryPercent(BATTERY_SYSTEM_USAGE, TOTAL_BATTERY_USAGE, HIDDEN_USAGE, DISCHARGE_AMOUNT)) .isWithin(PRECISION).of(PERCENT_SYSTEM_USAGE); } }
tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +1 −96 Original line number Diff line number Diff line Loading @@ -113,10 +113,6 @@ public class PowerUsageSummaryTest { @Mock private BatterySipper mScreenBatterySipper; @Mock private BatterySipper mOvercountedBatterySipper; @Mock private BatterySipper mSystemBatterySipper; @Mock private BatterySipper mCellBatterySipper; @Mock private PowerGaugePreference mPreference; Loading Loading @@ -184,16 +180,8 @@ public class PowerUsageSummaryTest { mFragment.setBatteryLayoutPreference(mBatteryLayoutPref); 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); mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERCOUNTED_USAGE; mUsageList = new ArrayList<>(); mUsageList.add(mNormalBatterySipper); mUsageList.add(mScreenBatterySipper); Loading Loading @@ -304,90 +292,7 @@ public class PowerUsageSummaryTest { } @Test public void testRemoveHiddenBatterySippers_containsHiddenSippers_removeAndReturnValue() { final List<BatterySipper> sippers = new ArrayList<>(); sippers.add(mNormalBatterySipper); sippers.add(mScreenBatterySipper); sippers.add(mSystemBatterySipper); sippers.add(mOvercountedBatterySipper); when(mFeatureFactory.powerUsageFeatureProvider.isTypeSystem(mSystemBatterySipper)) .thenReturn(true); final double totalUsage = mFragment.removeHiddenBatterySippers(sippers); assertThat(sippers).containsExactly(mNormalBatterySipper); assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE + BATTERY_SYSTEM_USAGE); } @Test public void testShouldHideSipper_typeIdle_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeOverCounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeWifi_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.WIFI; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeCell_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeScreen_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeBluetooth_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH; assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_typeSystem_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID); when(mFeatureFactory.powerUsageFeatureProvider.isTypeSystem(Matchers.<BatterySipper>any())) .thenReturn(true); assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_uidNormal_returnFalse() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isFalse(); } @Test public void testShouldHideSipper_typeService_returnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(UID); when(mFeatureFactory.powerUsageFeatureProvider.isTypeService(Matchers.<BatterySipper>any())) .thenReturn(true); assertThat(mFragment.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testSetUsageSummary_timeLessThanOneMinute_doNotSetSummary() { public void testSetUsageSummary_timeLessThanOneMinute_DoNotSetSummary() { final long usageTimeMs = 59 * DateUtils.SECOND_IN_MILLIS; mFragment.setUsageSummary(mPreference, "", usageTimeMs); Loading