Loading core/java/com/android/internal/os/BatterySipper.java +48 −4 Original line number Diff line number Diff line Loading @@ -17,15 +17,51 @@ package com.android.internal.os; import android.os.BatteryStats.Uid; import java.util.List; /** * Contains power usage of an application, system service, or hardware type. */ public class BatterySipper implements Comparable<BatterySipper> { public int userId; public Uid uidObj; public double totalPowerMah; public DrainType drainType; /** * Smeared power from screen usage. * We split the screen usage power and smear them among apps, based on activity time. */ public double screenPowerMah; /** * Smeared power using proportional method. * * we smear power usage from hidden sippers to all apps proportionally.(except for screen usage) * * @see BatteryStatsHelper#shouldHideSipper(BatterySipper) * @see BatteryStatsHelper#removeHiddenBatterySippers(List) */ public double proportionalSmearMah; /** * Total power that adding the smeared power. * * @see #sumPower() */ public double totalSmearedPowerMah; /** * Total power before smearing */ public double totalPowerMah; /** * Whether we should hide this sipper * * @see BatteryStatsHelper#shouldHideSipper(BatterySipper) */ public boolean shouldHide; /** * Generic usage time in milliseconds. */ Loading Loading @@ -169,15 +205,23 @@ public class BatterySipper implements Comparable<BatterySipper> { cameraPowerMah += other.cameraPowerMah; flashlightPowerMah += other.flashlightPowerMah; bluetoothPowerMah += other.bluetoothPowerMah; screenPowerMah += other.screenPowerMah; proportionalSmearMah += other.proportionalSmearMah; totalSmearedPowerMah += other.totalSmearedPowerMah; } /** * Sum all the powers and store the value into `value`. * Also sum the {@code smearedTotalPowerMah} by adding smeared powerMah. * * @return the sum of all the power in this BatterySipper. */ public double sumPower() { return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + flashlightPowerMah + bluetoothPowerMah; totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah; return totalPowerMah; } } core/java/com/android/internal/os/BatteryStatsHelper.java +236 −40 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.internal.os; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.SensorManager; import android.net.ConnectivityManager; import android.os.BatteryStats; Loading @@ -32,12 +34,16 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatterySipper.DrainType; import com.android.internal.util.ArrayUtils; import java.io.File; import java.io.FileInputStream; Loading @@ -55,7 +61,7 @@ import java.util.Locale; * The caller must initialize this class as soon as activity object is ready to use (for example, in * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). */ public final class BatteryStatsHelper { public class BatteryStatsHelper { static final boolean DEBUG = false; private static final String TAG = BatteryStatsHelper.class.getSimpleName(); Loading @@ -73,6 +79,10 @@ public final class BatteryStatsHelper { private Intent mBatteryBroadcast; private PowerProfile mPowerProfile; private String[] mSystemPackageArray; private String[] mServicepackageArray; private PackageManager mPackageManager; /** * List of apps using power. */ Loading Loading @@ -163,6 +173,13 @@ public final class BatteryStatsHelper { mContext = context; mCollectBatteryBroadcast = collectBatteryBroadcast; mWifiOnly = wifiOnly; mPackageManager = context.getPackageManager(); final Resources resources = context.getResources(); mSystemPackageArray = resources.getStringArray( com.android.internal.R.array.config_batteryPackageTypeSystem); mServicepackageArray = resources.getStringArray( com.android.internal.R.array.config_batteryPackageTypeService); } public void storeStatsHistoryInFile(String fname) { Loading Loading @@ -274,15 +291,25 @@ public final class BatteryStatsHelper { if (power == 0) return "0"; final String format; if (power < .00001) format = "%.8f"; else if (power < .0001) format = "%.7f"; else if (power < .001) format = "%.6f"; else if (power < .01) format = "%.5f"; else if (power < .1) format = "%.4f"; else if (power < 1) format = "%.3f"; else if (power < 10) format = "%.2f"; else if (power < 100) format = "%.1f"; else format = "%.0f"; if (power < .00001) { format = "%.8f"; } else if (power < .0001) { format = "%.7f"; } else if (power < .001) { format = "%.6f"; } else if (power < .01) { format = "%.5f"; } else if (power < .1) { format = "%.4f"; } else if (power < 1) { format = "%.3f"; } else if (power < 10) { format = "%.2f"; } else if (power < 100) { format = "%.1f"; } else { format = "%.0f"; } // Use English locale because this is never used in UI (only in checkin and dump). return String.format(Locale.ENGLISH, format, power); Loading Loading @@ -491,6 +518,21 @@ public final class BatteryStatsHelper { mMaxPower = Math.max(mMaxPower, amount); } } // Smear it! final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList); final double totalRemainingPower = getTotalPower() - hiddenPowerMah; if (Math.abs(totalRemainingPower) > 1e-3) { for (int i = 0, size = mUsageList.size(); i < size; i++) { final BatterySipper sipper = mUsageList.get(i); if (!sipper.shouldHide) { sipper.proportionalSmearMah = hiddenPowerMah * ((sipper.totalPowerMah + sipper.screenPowerMah) / totalRemainingPower); sipper.sumPower(); } } } } private void processAppUsage(SparseArray<UserHandle> asUsers) { Loading @@ -506,12 +548,15 @@ public final class BatteryStatsHelper { mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); final double totalPower = app.sumPower(); if (DEBUG && totalPower != 0) { Loading Loading @@ -647,7 +692,8 @@ public final class BatteryStatsHelper { */ private void addWiFiUsage() { BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0); mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); aggregateSippers(bs, mWifiSippers, "WIFI"); if (bs.totalPowerMah > 0) { mUsageList.add(bs); Loading Loading @@ -719,17 +765,29 @@ public final class BatteryStatsHelper { return mMobilemsppList; } public long getStatsPeriod() { return mStatsPeriod; } public long getStatsPeriod() { return mStatsPeriod; } public int getStatsType() { return mStatsType; } public int getStatsType() { return mStatsType; } public double getMaxPower() { return mMaxPower; } public double getMaxPower() { return mMaxPower; } public double getMaxRealPower() { return mMaxRealPower; } public double getMaxRealPower() { return mMaxRealPower; } public double getTotalPower() { return mTotalPower; } public double getTotalPower() { return mTotalPower; } public double getComputedPower() { return mComputedPower; } public double getComputedPower() { return mComputedPower; } public double getMinDrainedPower() { return mMinDrainedPower; Loading Loading @@ -765,6 +823,144 @@ public final class BatteryStatsHelper { } } /** * Mark 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<BatterySipper> sippers) { double proportionalSmearPowerMah = 0; BatterySipper screenSipper = null; for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper sipper = sippers.get(i); sipper.shouldHide = shouldHideSipper(sipper); if (sipper.shouldHide) { 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; } } smearScreenBatterySipper(sippers, screenSipper); return proportionalSmearPowerMah; } /** * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity * time. */ public void smearScreenBatterySipper(List<BatterySipper> 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.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0) / totalActivityTimeMs; } } } /** * 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.SCREEN || drainType == BatterySipper.DrainType.UNACCOUNTED || drainType == BatterySipper.DrainType.OVERCOUNTED || isTypeService(sipper) || isTypeSystem(sipper); } /** * Check whether {@code sipper} is type service */ public boolean isTypeService(BatterySipper sipper) { final String[] packages = mPackageManager.getPackagesForUid(sipper.getUid()); if (packages == null) { return false; } for (String packageName : packages) { if (ArrayUtils.contains(mServicepackageArray, packageName)) { return true; } } return false; } /** * Check whether {@code sipper} is type system */ public boolean isTypeSystem(BatterySipper sipper) { final int uid = sipper.uidObj == null ? -1 : sipper.getUid(); sipper.mPackages = mPackageManager.getPackagesForUid(uid); // Classify all the sippers to type system if the range of uid is 0...FIRST_APPLICATION_UID if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) { return true; } else if (sipper.mPackages != null) { for (final String packageName : sipper.mPackages) { if (ArrayUtils.contains(mSystemPackageArray, packageName)) { return true; } } } return false; } @VisibleForTesting public 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; } @VisibleForTesting public void setPackageManager(PackageManager packageManager) { mPackageManager = packageManager; } @VisibleForTesting public void setSystemPackageArray(String[] array) { mSystemPackageArray = array; } @VisibleForTesting public void setServicePackageArray(String[] array) { mServicepackageArray = array; } private void load() { if (mBatteryInfo == null) { return; Loading core/res/res/values/config.xml +10 −0 Original line number Diff line number Diff line Loading @@ -2968,4 +2968,14 @@ <!-- Name of a font family to use for headlines. If empty, falls back to platform default --> <string name="config_headlineFontFamily" translatable="false"></string> <!-- An array of packages that need to be treated as type system in battery settings --> <string-array translatable="false" name="config_batteryPackageTypeSystem"> <item>com.android.providers.calendar</item> <item>com.android.providers.media</item> <item>com.android.systemui</item> </string-array> <!-- An array of packages that need to be treated as type service in battery settings --> <string-array translatable="false" name="config_batteryPackageTypeService"/> </resources> core/res/res/values/symbols.xml +4 −0 Original line number Diff line number Diff line Loading @@ -3043,4 +3043,8 @@ <java-symbol type="string" name="config_headlineFontFamily" /> <java-symbol type="drawable" name="stat_sys_vitals" /> <java-symbol type="array" name="config_batteryPackageTypeSystem" /> <java-symbol type="array" name="config_batteryPackageTypeService" /> </resources> core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java 0 → 100644 +240 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.internal.os; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager; import android.os.BatteryStats; import android.os.Process; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.format.DateUtils; import junit.framework.TestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class BatteryStatsHelperTest extends TestCase { 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 = 123456; 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 BATTERY_UNACCOUNTED_USAGE = 700; private static final double BATTERY_APP_USAGE = 100; private static final double TOTAL_BATTERY_USAGE = 1000; 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 mUnaccountedBatterySipper; @Mock private BatterySipper mSystemBatterySipper; @Mock private BatterySipper mCellBatterySipper; @Mock private PackageManager mPackageManager; private BatteryStatsHelper mBatteryStatsHelper; private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE; when(mNormalBatterySipper.getUid()).thenReturn(UID); mNormalBatterySipper.uidObj = mUid; mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE; mSystemBatterySipper.drainType = BatterySipper.DrainType.APP; mSystemBatterySipper.totalPowerMah = BATTERY_SYSTEM_USAGE; mSystemBatterySipper.uidObj = mUid; when(mSystemBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID); mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERACCOUNTED_USAGE; mUnaccountedBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; mUnaccountedBatterySipper.totalPowerMah = BATTERY_UNACCOUNTED_USAGE; mContext = InstrumentationRegistry.getContext(); mBatteryStatsHelper = spy(new BatteryStatsHelper(mContext)); mBatteryStatsHelper.setPackageManager(mPackageManager); } @Test public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeOverAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeIdle_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeCell_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeScreen_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeSystem_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID); assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_UidNormal_ReturnFalse() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isFalse(); } @Test public void testRemoveHiddenBatterySippers_ContainsHiddenSippers_RemoveAndReturnValue() { final List<BatterySipper> sippers = new ArrayList<>(); sippers.add(mNormalBatterySipper); sippers.add(mScreenBatterySipper); sippers.add(mSystemBatterySipper); sippers.add(mOvercountedBatterySipper); sippers.add(mUnaccountedBatterySipper); doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper); doNothing().when(mBatteryStatsHelper).smearScreenBatterySipper(any(), any()); final double totalUsage = mBatteryStatsHelper.removeHiddenBatterySippers(sippers); assertThat(mNormalBatterySipper.shouldHide).isFalse(); assertThat(mScreenBatterySipper.shouldHide).isTrue(); assertThat(mSystemBatterySipper.shouldHide).isTrue(); assertThat(mOvercountedBatterySipper.shouldHide).isTrue(); assertThat(mUnaccountedBatterySipper.shouldHide).isTrue(); assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_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<BatterySipper> sippers = new ArrayList<>(); sippers.add(sipperNull); sippers.add(sipperBg); sippers.add(sipperFg); mBatteryStatsHelper.smearScreenBatterySipper(sippers, mScreenBatterySipper); assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0); assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0); assertThat(sipperFg.screenPowerMah).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE); } @Test public void testIsTypeSystem_systemPackage_returnTrue() { final String[] systemPackages = {"com.android.system"}; mBatteryStatsHelper.setSystemPackageArray(systemPackages); doReturn(UID).when(mNormalBatterySipper).getUid(); doReturn(systemPackages).when(mPackageManager).getPackagesForUid(UID); assertThat(mBatteryStatsHelper.isTypeSystem(mNormalBatterySipper)).isTrue(); } @Test public void testIsTypeService_servicePackage_returnTrue() { final String[] servicePackages = {"com.android.service"}; mBatteryStatsHelper.setServicePackageArray(servicePackages); doReturn(UID).when(mNormalBatterySipper).getUid(); doReturn(servicePackages).when(mPackageManager).getPackagesForUid(UID); assertThat(mBatteryStatsHelper.isTypeService(mNormalBatterySipper)).isTrue(); } 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(mBatteryStatsHelper).getForegroundActivityTotalTimeMs( eq(uid), anyLong()); doReturn(uidCode).when(uid).getUid(); sipper.uidObj = uid; } return sipper; } } Loading
core/java/com/android/internal/os/BatterySipper.java +48 −4 Original line number Diff line number Diff line Loading @@ -17,15 +17,51 @@ package com.android.internal.os; import android.os.BatteryStats.Uid; import java.util.List; /** * Contains power usage of an application, system service, or hardware type. */ public class BatterySipper implements Comparable<BatterySipper> { public int userId; public Uid uidObj; public double totalPowerMah; public DrainType drainType; /** * Smeared power from screen usage. * We split the screen usage power and smear them among apps, based on activity time. */ public double screenPowerMah; /** * Smeared power using proportional method. * * we smear power usage from hidden sippers to all apps proportionally.(except for screen usage) * * @see BatteryStatsHelper#shouldHideSipper(BatterySipper) * @see BatteryStatsHelper#removeHiddenBatterySippers(List) */ public double proportionalSmearMah; /** * Total power that adding the smeared power. * * @see #sumPower() */ public double totalSmearedPowerMah; /** * Total power before smearing */ public double totalPowerMah; /** * Whether we should hide this sipper * * @see BatteryStatsHelper#shouldHideSipper(BatterySipper) */ public boolean shouldHide; /** * Generic usage time in milliseconds. */ Loading Loading @@ -169,15 +205,23 @@ public class BatterySipper implements Comparable<BatterySipper> { cameraPowerMah += other.cameraPowerMah; flashlightPowerMah += other.flashlightPowerMah; bluetoothPowerMah += other.bluetoothPowerMah; screenPowerMah += other.screenPowerMah; proportionalSmearMah += other.proportionalSmearMah; totalSmearedPowerMah += other.totalSmearedPowerMah; } /** * Sum all the powers and store the value into `value`. * Also sum the {@code smearedTotalPowerMah} by adding smeared powerMah. * * @return the sum of all the power in this BatterySipper. */ public double sumPower() { return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + flashlightPowerMah + bluetoothPowerMah; totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah; return totalPowerMah; } }
core/java/com/android/internal/os/BatteryStatsHelper.java +236 −40 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.internal.os; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.SensorManager; import android.net.ConnectivityManager; import android.os.BatteryStats; Loading @@ -32,12 +34,16 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatterySipper.DrainType; import com.android.internal.util.ArrayUtils; import java.io.File; import java.io.FileInputStream; Loading @@ -55,7 +61,7 @@ import java.util.Locale; * The caller must initialize this class as soon as activity object is ready to use (for example, in * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). */ public final class BatteryStatsHelper { public class BatteryStatsHelper { static final boolean DEBUG = false; private static final String TAG = BatteryStatsHelper.class.getSimpleName(); Loading @@ -73,6 +79,10 @@ public final class BatteryStatsHelper { private Intent mBatteryBroadcast; private PowerProfile mPowerProfile; private String[] mSystemPackageArray; private String[] mServicepackageArray; private PackageManager mPackageManager; /** * List of apps using power. */ Loading Loading @@ -163,6 +173,13 @@ public final class BatteryStatsHelper { mContext = context; mCollectBatteryBroadcast = collectBatteryBroadcast; mWifiOnly = wifiOnly; mPackageManager = context.getPackageManager(); final Resources resources = context.getResources(); mSystemPackageArray = resources.getStringArray( com.android.internal.R.array.config_batteryPackageTypeSystem); mServicepackageArray = resources.getStringArray( com.android.internal.R.array.config_batteryPackageTypeService); } public void storeStatsHistoryInFile(String fname) { Loading Loading @@ -274,15 +291,25 @@ public final class BatteryStatsHelper { if (power == 0) return "0"; final String format; if (power < .00001) format = "%.8f"; else if (power < .0001) format = "%.7f"; else if (power < .001) format = "%.6f"; else if (power < .01) format = "%.5f"; else if (power < .1) format = "%.4f"; else if (power < 1) format = "%.3f"; else if (power < 10) format = "%.2f"; else if (power < 100) format = "%.1f"; else format = "%.0f"; if (power < .00001) { format = "%.8f"; } else if (power < .0001) { format = "%.7f"; } else if (power < .001) { format = "%.6f"; } else if (power < .01) { format = "%.5f"; } else if (power < .1) { format = "%.4f"; } else if (power < 1) { format = "%.3f"; } else if (power < 10) { format = "%.2f"; } else if (power < 100) { format = "%.1f"; } else { format = "%.0f"; } // Use English locale because this is never used in UI (only in checkin and dump). return String.format(Locale.ENGLISH, format, power); Loading Loading @@ -491,6 +518,21 @@ public final class BatteryStatsHelper { mMaxPower = Math.max(mMaxPower, amount); } } // Smear it! final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList); final double totalRemainingPower = getTotalPower() - hiddenPowerMah; if (Math.abs(totalRemainingPower) > 1e-3) { for (int i = 0, size = mUsageList.size(); i < size; i++) { final BatterySipper sipper = mUsageList.get(i); if (!sipper.shouldHide) { sipper.proportionalSmearMah = hiddenPowerMah * ((sipper.totalPowerMah + sipper.screenPowerMah) / totalRemainingPower); sipper.sumPower(); } } } } private void processAppUsage(SparseArray<UserHandle> asUsers) { Loading @@ -506,12 +548,15 @@ public final class BatteryStatsHelper { mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); final double totalPower = app.sumPower(); if (DEBUG && totalPower != 0) { Loading Loading @@ -647,7 +692,8 @@ public final class BatteryStatsHelper { */ private void addWiFiUsage() { BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0); mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); aggregateSippers(bs, mWifiSippers, "WIFI"); if (bs.totalPowerMah > 0) { mUsageList.add(bs); Loading Loading @@ -719,17 +765,29 @@ public final class BatteryStatsHelper { return mMobilemsppList; } public long getStatsPeriod() { return mStatsPeriod; } public long getStatsPeriod() { return mStatsPeriod; } public int getStatsType() { return mStatsType; } public int getStatsType() { return mStatsType; } public double getMaxPower() { return mMaxPower; } public double getMaxPower() { return mMaxPower; } public double getMaxRealPower() { return mMaxRealPower; } public double getMaxRealPower() { return mMaxRealPower; } public double getTotalPower() { return mTotalPower; } public double getTotalPower() { return mTotalPower; } public double getComputedPower() { return mComputedPower; } public double getComputedPower() { return mComputedPower; } public double getMinDrainedPower() { return mMinDrainedPower; Loading Loading @@ -765,6 +823,144 @@ public final class BatteryStatsHelper { } } /** * Mark 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<BatterySipper> sippers) { double proportionalSmearPowerMah = 0; BatterySipper screenSipper = null; for (int i = sippers.size() - 1; i >= 0; i--) { final BatterySipper sipper = sippers.get(i); sipper.shouldHide = shouldHideSipper(sipper); if (sipper.shouldHide) { 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; } } smearScreenBatterySipper(sippers, screenSipper); return proportionalSmearPowerMah; } /** * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity * time. */ public void smearScreenBatterySipper(List<BatterySipper> 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.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0) / totalActivityTimeMs; } } } /** * 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.SCREEN || drainType == BatterySipper.DrainType.UNACCOUNTED || drainType == BatterySipper.DrainType.OVERCOUNTED || isTypeService(sipper) || isTypeSystem(sipper); } /** * Check whether {@code sipper} is type service */ public boolean isTypeService(BatterySipper sipper) { final String[] packages = mPackageManager.getPackagesForUid(sipper.getUid()); if (packages == null) { return false; } for (String packageName : packages) { if (ArrayUtils.contains(mServicepackageArray, packageName)) { return true; } } return false; } /** * Check whether {@code sipper} is type system */ public boolean isTypeSystem(BatterySipper sipper) { final int uid = sipper.uidObj == null ? -1 : sipper.getUid(); sipper.mPackages = mPackageManager.getPackagesForUid(uid); // Classify all the sippers to type system if the range of uid is 0...FIRST_APPLICATION_UID if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) { return true; } else if (sipper.mPackages != null) { for (final String packageName : sipper.mPackages) { if (ArrayUtils.contains(mSystemPackageArray, packageName)) { return true; } } } return false; } @VisibleForTesting public 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; } @VisibleForTesting public void setPackageManager(PackageManager packageManager) { mPackageManager = packageManager; } @VisibleForTesting public void setSystemPackageArray(String[] array) { mSystemPackageArray = array; } @VisibleForTesting public void setServicePackageArray(String[] array) { mServicepackageArray = array; } private void load() { if (mBatteryInfo == null) { return; Loading
core/res/res/values/config.xml +10 −0 Original line number Diff line number Diff line Loading @@ -2968,4 +2968,14 @@ <!-- Name of a font family to use for headlines. If empty, falls back to platform default --> <string name="config_headlineFontFamily" translatable="false"></string> <!-- An array of packages that need to be treated as type system in battery settings --> <string-array translatable="false" name="config_batteryPackageTypeSystem"> <item>com.android.providers.calendar</item> <item>com.android.providers.media</item> <item>com.android.systemui</item> </string-array> <!-- An array of packages that need to be treated as type service in battery settings --> <string-array translatable="false" name="config_batteryPackageTypeService"/> </resources>
core/res/res/values/symbols.xml +4 −0 Original line number Diff line number Diff line Loading @@ -3043,4 +3043,8 @@ <java-symbol type="string" name="config_headlineFontFamily" /> <java-symbol type="drawable" name="stat_sys_vitals" /> <java-symbol type="array" name="config_batteryPackageTypeSystem" /> <java-symbol type="array" name="config_batteryPackageTypeService" /> </resources>
core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java 0 → 100644 +240 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.internal.os; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager; import android.os.BatteryStats; import android.os.Process; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.format.DateUtils; import junit.framework.TestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class BatteryStatsHelperTest extends TestCase { 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 = 123456; 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 BATTERY_UNACCOUNTED_USAGE = 700; private static final double BATTERY_APP_USAGE = 100; private static final double TOTAL_BATTERY_USAGE = 1000; 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 mUnaccountedBatterySipper; @Mock private BatterySipper mSystemBatterySipper; @Mock private BatterySipper mCellBatterySipper; @Mock private PackageManager mPackageManager; private BatteryStatsHelper mBatteryStatsHelper; private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE; when(mNormalBatterySipper.getUid()).thenReturn(UID); mNormalBatterySipper.uidObj = mUid; mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE; mSystemBatterySipper.drainType = BatterySipper.DrainType.APP; mSystemBatterySipper.totalPowerMah = BATTERY_SYSTEM_USAGE; mSystemBatterySipper.uidObj = mUid; when(mSystemBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID); mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERACCOUNTED_USAGE; mUnaccountedBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; mUnaccountedBatterySipper.totalPowerMah = BATTERY_UNACCOUNTED_USAGE; mContext = InstrumentationRegistry.getContext(); mBatteryStatsHelper = spy(new BatteryStatsHelper(mContext)); mBatteryStatsHelper.setPackageManager(mPackageManager); } @Test public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeOverAccounted_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeIdle_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeCell_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeScreen_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_TypeSystem_ReturnTrue() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID); assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); } @Test public void testShouldHideSipper_UidNormal_ReturnFalse() { mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isFalse(); } @Test public void testRemoveHiddenBatterySippers_ContainsHiddenSippers_RemoveAndReturnValue() { final List<BatterySipper> sippers = new ArrayList<>(); sippers.add(mNormalBatterySipper); sippers.add(mScreenBatterySipper); sippers.add(mSystemBatterySipper); sippers.add(mOvercountedBatterySipper); sippers.add(mUnaccountedBatterySipper); doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper); doNothing().when(mBatteryStatsHelper).smearScreenBatterySipper(any(), any()); final double totalUsage = mBatteryStatsHelper.removeHiddenBatterySippers(sippers); assertThat(mNormalBatterySipper.shouldHide).isFalse(); assertThat(mScreenBatterySipper.shouldHide).isTrue(); assertThat(mSystemBatterySipper.shouldHide).isTrue(); assertThat(mOvercountedBatterySipper.shouldHide).isTrue(); assertThat(mUnaccountedBatterySipper.shouldHide).isTrue(); assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_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<BatterySipper> sippers = new ArrayList<>(); sippers.add(sipperNull); sippers.add(sipperBg); sippers.add(sipperFg); mBatteryStatsHelper.smearScreenBatterySipper(sippers, mScreenBatterySipper); assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0); assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0); assertThat(sipperFg.screenPowerMah).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE); } @Test public void testIsTypeSystem_systemPackage_returnTrue() { final String[] systemPackages = {"com.android.system"}; mBatteryStatsHelper.setSystemPackageArray(systemPackages); doReturn(UID).when(mNormalBatterySipper).getUid(); doReturn(systemPackages).when(mPackageManager).getPackagesForUid(UID); assertThat(mBatteryStatsHelper.isTypeSystem(mNormalBatterySipper)).isTrue(); } @Test public void testIsTypeService_servicePackage_returnTrue() { final String[] servicePackages = {"com.android.service"}; mBatteryStatsHelper.setServicePackageArray(servicePackages); doReturn(UID).when(mNormalBatterySipper).getUid(); doReturn(servicePackages).when(mPackageManager).getPackagesForUid(UID); assertThat(mBatteryStatsHelper.isTypeService(mNormalBatterySipper)).isTrue(); } 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(mBatteryStatsHelper).getForegroundActivityTotalTimeMs( eq(uid), anyLong()); doReturn(uidCode).when(uid).getUid(); sipper.uidObj = uid; } return sipper; } }