diff options
| -rw-r--r-- | core/java/com/android/internal/os/BatterySipper.java | 52 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/BatteryStatsHelper.java | 276 | ||||
| -rw-r--r-- | core/res/res/values/config.xml | 10 | ||||
| -rw-r--r-- | core/res/res/values/symbols.xml | 4 | ||||
| -rw-r--r-- | core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java | 240 |
5 files changed, 538 insertions, 44 deletions
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java index 5ea9475dbd9a..5457c1d4cc03 100644 --- a/core/java/com/android/internal/os/BatterySipper.java +++ b/core/java/com/android/internal/os/BatterySipper.java @@ -17,16 +17,52 @@ 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. */ public long usageTimeMs; @@ -99,8 +135,8 @@ public class BatterySipper implements Comparable<BatterySipper> { } public void computeMobilemspp() { - long packets = mobileRxPackets+mobileTxPackets; - mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0; + long packets = mobileRxPackets + mobileTxPackets; + mobilemspp = packets > 0 ? (mobileActive / (double) packets) : 0; } @Override @@ -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; } } diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 1ae5c6625652..f3632f08880d 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -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; @@ -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; @@ -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(); @@ -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. */ @@ -131,7 +141,7 @@ public final class BatteryStatsHelper { boolean mHasBluetoothPowerReporting = false; public static boolean checkWifiOnly(Context context) { - ConnectivityManager cm = (ConnectivityManager)context.getSystemService( + ConnectivityManager cm = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE); return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); } @@ -144,7 +154,7 @@ public final class BatteryStatsHelper { } public static boolean checkHasBluetoothPowerReporting(BatteryStats stats, - PowerProfile profile) { + PowerProfile profile) { return stats.hasBluetoothActivityReporting() && profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE) != 0 && profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX) != 0 && @@ -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) { @@ -216,7 +233,7 @@ public final class BatteryStatsHelper { } } return getStats(IBatteryStats.Stub.asInterface( - ServiceManager.getService(BatteryStats.SERVICE_NAME))); + ServiceManager.getService(BatteryStats.SERVICE_NAME))); } public static void dropFile(Context context, String fname) { @@ -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); @@ -370,7 +397,7 @@ public final class BatteryStatsHelper { mWifiPowerCalculator.reset(); final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats, - mPowerProfile); + mPowerProfile); if (mBluetoothPowerCalculator == null || hasBluetoothPowerReporting != mHasBluetoothPowerReporting) { mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile); @@ -405,12 +432,12 @@ public final class BatteryStatsHelper { mChargeTimeRemainingUs = mStats.computeChargeTimeRemaining(rawRealtimeUs); if (DEBUG) { - Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime=" - + (rawUptimeUs/1000)); - Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtimeUs /1000) + " uptime=" - + (mBatteryUptimeUs /1000)); - Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtimeUs /1000) + " uptime=" - + (mTypeBatteryUptimeUs /1000)); + Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs / 1000) + " uptime=" + + (rawUptimeUs / 1000)); + Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtimeUs / 1000) + " uptime=" + + (mBatteryUptimeUs / 1000)); + Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtimeUs / 1000) + " uptime=" + + (mTypeBatteryUptimeUs / 1000)); } mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge() * mPowerProfile.getBatteryCapacity()) / 100; @@ -420,7 +447,7 @@ public final class BatteryStatsHelper { processAppUsage(asUsers); // Before aggregating apps in to users, collect all apps to sort by their ms per packet. - for (int i=0; i<mUsageList.size(); i++) { + for (int i = 0; i < mUsageList.size(); i++) { BatterySipper bs = mUsageList.get(i); bs.computeMobilemspp(); if (bs.mobilemspp != 0) { @@ -428,9 +455,9 @@ public final class BatteryStatsHelper { } } - for (int i=0; i<mUserSippers.size(); i++) { + for (int i = 0; i < mUserSippers.size(); i++) { List<BatterySipper> user = mUserSippers.valueAt(i); - for (int j=0; j<user.size(); j++) { + for (int j = 0; j < user.size(); j++) { BatterySipper bs = user.get(j); bs.computeMobilemspp(); if (bs.mobilemspp != 0) { @@ -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) { @@ -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) { @@ -562,7 +607,7 @@ public final class BatteryStatsHelper { private void addPhoneUsage() { long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, mStatsType) / 1000; double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) - * phoneOnTimeMs / (60*60*1000); + * phoneOnTimeMs / (60 * 60 * 1000); if (phoneOnPower != 0) { addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); } @@ -582,14 +627,14 @@ public final class BatteryStatsHelper { / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtimeUs, mStatsType) / 1000; - double p = screenBinPower*brightnessTime; + double p = screenBinPower * brightnessTime; if (DEBUG && p != 0) { Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime + " power=" + makemAh(p / (60 * 60 * 1000))); } power += p; } - power /= (60*60*1000); // To hours + power /= (60 * 60 * 1000); // To hours if (power != 0) { addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power); } @@ -606,7 +651,7 @@ public final class BatteryStatsHelper { } private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) { - for (int i=0; i<from.size(); i++) { + for (int i = 0; i < from.size(); i++) { BatterySipper wbs = from.get(i); if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTimeMs); bs.add(wbs); @@ -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); @@ -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; @@ -747,7 +805,7 @@ public final class BatteryStatsHelper { int pos = 0; byte[] data = new byte[avail]; while (true) { - int amt = stream.read(data, pos, data.length-pos); + int amt = stream.read(data, pos, data.length - pos); //Log.i("foo", "Read " + amt + " bytes at " + pos // + " of avail " + data.length); if (amt <= 0) { @@ -757,14 +815,152 @@ public final class BatteryStatsHelper { } pos += amt; avail = stream.available(); - if (avail > data.length-pos) { - byte[] newData = new byte[pos+avail]; + if (avail > data.length - pos) { + byte[] newData = new byte[pos + avail]; System.arraycopy(data, 0, newData, 0, pos); data = newData; } } } + /** + * 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; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1959245f0626..b8c9933d199b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ef7380032b90..69b33d1ad915 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3046,4 +3046,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> diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java new file mode 100644 index 000000000000..f01c33f7ed2e --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java @@ -0,0 +1,240 @@ +/* + * 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; + } + + +} |