/* * Copyright (C) 2009 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 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; import android.os.BatteryStats.Uid; import android.os.Bundle; import android.os.MemoryFile; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; 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; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; /** * A helper class for retrieving the power usage information for all applications and services. * * 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 class BatteryStatsHelper { static final boolean DEBUG = false; private static final String TAG = BatteryStatsHelper.class.getSimpleName(); private static BatteryStats sStatsXfer; private static Intent sBatteryBroadcastXfer; private static ArrayMap sFileXfer = new ArrayMap<>(); final private Context mContext; final private boolean mCollectBatteryBroadcast; final private boolean mWifiOnly; private IBatteryStats mBatteryInfo; private BatteryStats mStats; private Intent mBatteryBroadcast; private PowerProfile mPowerProfile; private String[] mSystemPackageArray; private String[] mServicepackageArray; private PackageManager mPackageManager; /** * List of apps using power. */ private final List mUsageList = new ArrayList<>(); /** * List of apps using wifi power. */ private final List mWifiSippers = new ArrayList<>(); /** * List of apps using bluetooth power. */ private final List mBluetoothSippers = new ArrayList<>(); private final SparseArray> mUserSippers = new SparseArray<>(); private final List mMobilemsppList = new ArrayList<>(); private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; long mRawRealtimeUs; long mRawUptimeUs; long mBatteryRealtimeUs; long mBatteryUptimeUs; long mTypeBatteryRealtimeUs; long mTypeBatteryUptimeUs; long mBatteryTimeRemainingUs; long mChargeTimeRemainingUs; private long mStatsPeriod = 0; // The largest entry by power. private double mMaxPower = 1; // The largest real entry by power (not undercounted or overcounted). private double mMaxRealPower = 1; // Total computed power. private double mComputedPower; private double mTotalPower; private double mMinDrainedPower; private double mMaxDrainedPower; PowerCalculator mCpuPowerCalculator; PowerCalculator mWakelockPowerCalculator; MobileRadioPowerCalculator mMobileRadioPowerCalculator; PowerCalculator mWifiPowerCalculator; PowerCalculator mBluetoothPowerCalculator; PowerCalculator mSensorPowerCalculator; PowerCalculator mCameraPowerCalculator; PowerCalculator mFlashlightPowerCalculator; PowerCalculator mMemoryPowerCalculator; boolean mHasWifiPowerReporting = false; boolean mHasBluetoothPowerReporting = false; public static boolean checkWifiOnly(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE); return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); } public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) { return stats.hasWifiActivityReporting() && profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 && profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 && profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0; } public static boolean checkHasBluetoothPowerReporting(BatteryStats stats, PowerProfile profile) { return stats.hasBluetoothActivityReporting() && profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE) != 0 && profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX) != 0 && profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX) != 0; } public BatteryStatsHelper(Context context) { this(context, true); } public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) { this(context, collectBatteryBroadcast, checkWifiOnly(context)); } public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) { 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) { synchronized (sFileXfer) { File path = makeFilePath(mContext, fname); sFileXfer.put(path, this.getStats()); FileOutputStream fout = null; try { fout = new FileOutputStream(path); Parcel hist = Parcel.obtain(); getStats().writeToParcelWithoutUids(hist, 0); byte[] histData = hist.marshall(); fout.write(histData); } catch (IOException e) { Log.w(TAG, "Unable to write history to file", e); } finally { if (fout != null) { try { fout.close(); } catch (IOException e) { } } } } } public static BatteryStats statsFromFile(Context context, String fname) { synchronized (sFileXfer) { File path = makeFilePath(context, fname); BatteryStats stats = sFileXfer.get(path); if (stats != null) { return stats; } FileInputStream fin = null; try { fin = new FileInputStream(path); byte[] data = readFully(fin); Parcel parcel = Parcel.obtain(); parcel.unmarshall(data, 0, data.length); parcel.setDataPosition(0); return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel); } catch (IOException e) { Log.w(TAG, "Unable to read history to file", e); } finally { if (fin != null) { try { fin.close(); } catch (IOException e) { } } } } return getStats(IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME))); } public static void dropFile(Context context, String fname) { makeFilePath(context, fname).delete(); } private static File makeFilePath(Context context, String fname) { return new File(context.getFilesDir(), fname); } /** Clears the current stats and forces recreating for future use. */ public void clearStats() { mStats = null; } public BatteryStats getStats() { if (mStats == null) { load(); } return mStats; } public Intent getBatteryBroadcast() { if (mBatteryBroadcast == null && mCollectBatteryBroadcast) { load(); } return mBatteryBroadcast; } public PowerProfile getPowerProfile() { return mPowerProfile; } public void create(BatteryStats stats) { mPowerProfile = new PowerProfile(mContext); mStats = stats; } public void create(Bundle icicle) { if (icicle != null) { mStats = sStatsXfer; mBatteryBroadcast = sBatteryBroadcastXfer; } mBatteryInfo = IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)); mPowerProfile = new PowerProfile(mContext); } public void storeState() { sStatsXfer = mStats; sBatteryBroadcastXfer = mBatteryBroadcast; } public static String makemAh(double power) { 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"; } // Use English locale because this is never used in UI (only in checkin and dump). return String.format(Locale.ENGLISH, format, power); } /** * Refreshes the power usage list. */ public void refreshStats(int statsType, int asUser) { SparseArray users = new SparseArray<>(1); users.put(asUser, new UserHandle(asUser)); refreshStats(statsType, users); } /** * Refreshes the power usage list. */ public void refreshStats(int statsType, List asUsers) { final int n = asUsers.size(); SparseArray users = new SparseArray<>(n); for (int i = 0; i < n; ++i) { UserHandle userHandle = asUsers.get(i); users.put(userHandle.getIdentifier(), userHandle); } refreshStats(statsType, users); } /** * Refreshes the power usage list. */ public void refreshStats(int statsType, SparseArray asUsers) { refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000, SystemClock.uptimeMillis() * 1000); } public void refreshStats(int statsType, SparseArray asUsers, long rawRealtimeUs, long rawUptimeUs) { // Initialize mStats if necessary. getStats(); mMaxPower = 0; mMaxRealPower = 0; mComputedPower = 0; mTotalPower = 0; mUsageList.clear(); mWifiSippers.clear(); mBluetoothSippers.clear(); mUserSippers.clear(); mMobilemsppList.clear(); if (mStats == null) { return; } if (mCpuPowerCalculator == null) { mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile); } mCpuPowerCalculator.reset(); if (mMemoryPowerCalculator == null) { mMemoryPowerCalculator = new MemoryPowerCalculator(mPowerProfile); } mMemoryPowerCalculator.reset(); if (mWakelockPowerCalculator == null) { mWakelockPowerCalculator = new WakelockPowerCalculator(mPowerProfile); } mWakelockPowerCalculator.reset(); if (mMobileRadioPowerCalculator == null) { mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile, mStats); } mMobileRadioPowerCalculator.reset(mStats); // checkHasWifiPowerReporting can change if we get energy data at a later point, so // always check this field. final boolean hasWifiPowerReporting = checkHasWifiPowerReporting(mStats, mPowerProfile); if (mWifiPowerCalculator == null || hasWifiPowerReporting != mHasWifiPowerReporting) { mWifiPowerCalculator = hasWifiPowerReporting ? new WifiPowerCalculator(mPowerProfile) : new WifiPowerEstimator(mPowerProfile); mHasWifiPowerReporting = hasWifiPowerReporting; } mWifiPowerCalculator.reset(); final boolean hasBluetoothPowerReporting = checkHasBluetoothPowerReporting(mStats, mPowerProfile); if (mBluetoothPowerCalculator == null || hasBluetoothPowerReporting != mHasBluetoothPowerReporting) { mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile); mHasBluetoothPowerReporting = hasBluetoothPowerReporting; } mBluetoothPowerCalculator.reset(); if (mSensorPowerCalculator == null) { mSensorPowerCalculator = new SensorPowerCalculator(mPowerProfile, (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE)); } mSensorPowerCalculator.reset(); if (mCameraPowerCalculator == null) { mCameraPowerCalculator = new CameraPowerCalculator(mPowerProfile); } mCameraPowerCalculator.reset(); if (mFlashlightPowerCalculator == null) { mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile); } mFlashlightPowerCalculator.reset(); mStatsType = statsType; mRawUptimeUs = rawUptimeUs; mRawRealtimeUs = rawRealtimeUs; mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs); mBatteryRealtimeUs = mStats.getBatteryRealtime(rawRealtimeUs); mTypeBatteryUptimeUs = mStats.computeBatteryUptime(rawUptimeUs, mStatsType); mTypeBatteryRealtimeUs = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); mBatteryTimeRemainingUs = mStats.computeBatteryTimeRemaining(rawRealtimeUs); 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)); } mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge() * mPowerProfile.getBatteryCapacity()) / 100; mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() * mPowerProfile.getBatteryCapacity()) / 100; 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++) { BatterySipper bs = mUsageList.get(i); bs.computeMobilemspp(); if (bs.mobilemspp != 0) { mMobilemsppList.add(bs); } } for (int i = 0; i < mUserSippers.size(); i++) { List user = mUserSippers.valueAt(i); for (int j = 0; j < user.size(); j++) { BatterySipper bs = user.get(j); bs.computeMobilemspp(); if (bs.mobilemspp != 0) { mMobilemsppList.add(bs); } } } Collections.sort(mMobilemsppList, new Comparator() { @Override public int compare(BatterySipper lhs, BatterySipper rhs) { return Double.compare(rhs.mobilemspp, lhs.mobilemspp); } }); processMiscUsage(); Collections.sort(mUsageList); // At this point, we've sorted the list so we are guaranteed the max values are at the top. // We have only added real powers so far. if (!mUsageList.isEmpty()) { mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah; final int usageListCount = mUsageList.size(); for (int i = 0; i < usageListCount; i++) { mComputedPower += mUsageList.get(i).totalPowerMah; } } if (DEBUG) { Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge=" + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower)); } mTotalPower = mComputedPower; if (mStats.getLowDischargeAmountSinceCharge() > 1) { if (mMinDrainedPower > mComputedPower) { double amount = mMinDrainedPower - mComputedPower; mTotalPower = mMinDrainedPower; BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount); // Insert the BatterySipper in its sorted position. int index = Collections.binarySearch(mUsageList, bs); if (index < 0) { index = -(index + 1); } mUsageList.add(index, bs); mMaxPower = Math.max(mMaxPower, amount); } else if (mMaxDrainedPower < mComputedPower) { double amount = mComputedPower - mMaxDrainedPower; // Insert the BatterySipper in its sorted position. BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount); int index = Collections.binarySearch(mUsageList, bs); if (index < 0) { index = -(index + 1); } mUsageList.add(index, bs); 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 asUsers) { final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null); mStatsPeriod = mTypeBatteryRealtimeUs; BatterySipper osSipper = null; final SparseArray uidStats = mStats.getUidStats(); final int NU = uidStats.size(); for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0); mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mWakelockPowerCalculator.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); mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType); final double totalPower = app.sumPower(); if (DEBUG && totalPower != 0) { Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(), makemAh(totalPower))); } // Add the app to the list if it is consuming power. if (totalPower != 0 || u.getUid() == 0) { // // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list. // final int uid = app.getUid(); final int userId = UserHandle.getUserId(uid); if (uid == Process.WIFI_UID) { mWifiSippers.add(app); } else if (uid == Process.BLUETOOTH_UID) { mBluetoothSippers.add(app); } else if (!forAllUsers && asUsers.get(userId) == null && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) { // We are told to just report this user's apps as one large entry. List list = mUserSippers.get(userId); if (list == null) { list = new ArrayList<>(); mUserSippers.put(userId, list); } list.add(app); } else { mUsageList.add(app); } if (uid == 0) { osSipper = app; } } } if (osSipper != null) { // The device has probably been awake for longer than the screen on // time and application wake lock time would account for. Assign // this remainder to the OS, if possible. mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); osSipper.sumPower(); } } private void addPhoneUsage() { long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, mStatsType) / 1000; double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) * phoneOnTimeMs / (60 * 60 * 1000); if (phoneOnPower != 0) { addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); } } /** * Screen power is the additional power the screen takes while the device is running. */ private void addScreenUsage() { double power = 0; long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtimeUs, mStatsType) / 1000; power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON); final double screenFullPower = mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { double screenBinPower = screenFullPower * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtimeUs, mStatsType) / 1000; 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 if (power != 0) { addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power); } } private void addRadioUsage() { BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0); mMobileRadioPowerCalculator.calculateRemaining(radio, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); radio.sumPower(); if (radio.totalPowerMah > 0) { mUsageList.add(radio); } } private void aggregateSippers(BatterySipper bs, List from, String tag) { 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); } bs.computeMobilemspp(); bs.sumPower(); } /** * Calculate the baseline power usage for the device when it is in suspend and idle. * The device is drawing POWER_CPU_IDLE power at its lowest power state. * The device is drawing POWER_CPU_IDLE + POWER_CPU_AWAKE power when a wakelock is held. */ private void addIdleUsage() { final double suspendPowerMaMs = (mTypeBatteryRealtimeUs / 1000) * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE); final double idlePowerMaMs = (mTypeBatteryUptimeUs / 1000) * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE); final double totalPowerMah = (suspendPowerMaMs + idlePowerMaMs) / (60 * 60 * 1000); if (DEBUG && totalPowerMah != 0) { Log.d(TAG, "Suspend: time=" + (mTypeBatteryRealtimeUs / 1000) + " power=" + makemAh(suspendPowerMaMs / (60 * 60 * 1000))); Log.d(TAG, "Idle: time=" + (mTypeBatteryUptimeUs / 1000) + " power=" + makemAh(idlePowerMaMs / (60 * 60 * 1000))); } if (totalPowerMah != 0) { addEntry(BatterySipper.DrainType.IDLE, mTypeBatteryRealtimeUs / 1000, totalPowerMah); } } /** * We do per-app blaming of WiFi activity. If energy info is reported from the controller, * then only the WiFi process gets blamed here since we normalize power calculations and * assign all the power drain to apps. If energy info is not reported, we attribute the * difference between total running time of WiFi for all apps and the actual running time * of WiFi to the WiFi subsystem. */ private void addWiFiUsage() { BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0); mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); aggregateSippers(bs, mWifiSippers, "WIFI"); if (bs.totalPowerMah > 0) { mUsageList.add(bs); } } /** * Bluetooth usage is not attributed to any apps yet, so the entire blame goes to the * Bluetooth Category. */ private void addBluetoothUsage() { BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0); mBluetoothPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); aggregateSippers(bs, mBluetoothSippers, "Bluetooth"); if (bs.totalPowerMah > 0) { mUsageList.add(bs); } } private void addUserUsage() { for (int i = 0; i < mUserSippers.size(); i++) { final int userId = mUserSippers.keyAt(i); BatterySipper bs = new BatterySipper(DrainType.USER, null, 0); bs.userId = userId; aggregateSippers(bs, mUserSippers.valueAt(i), "User"); mUsageList.add(bs); } } private void addMemoryUsage() { BatterySipper memory = new BatterySipper(DrainType.MEMORY, null, 0); mMemoryPowerCalculator.calculateRemaining(memory, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType); memory.sumPower(); if (memory.totalPowerMah > 0) { mUsageList.add(memory); } } private void processMiscUsage() { addUserUsage(); addPhoneUsage(); addScreenUsage(); addWiFiUsage(); addBluetoothUsage(); addMemoryUsage(); addIdleUsage(); // Not including cellular idle power // Don't compute radio usage if it's a wifi-only device if (!mWifiOnly) { addRadioUsage(); } } private BatterySipper addEntry(DrainType drainType, long time, double power) { BatterySipper bs = new BatterySipper(drainType, null, 0); bs.usagePowerMah = power; bs.usageTimeMs = time; bs.sumPower(); mUsageList.add(bs); return bs; } public List getUsageList() { return mUsageList; } public List getMobilemsppList() { return mMobilemsppList; } public long getStatsPeriod() { return mStatsPeriod; } public int getStatsType() { return mStatsType; } public double getMaxPower() { return mMaxPower; } public double getMaxRealPower() { return mMaxRealPower; } public double getTotalPower() { return mTotalPower; } public double getComputedPower() { return mComputedPower; } public double getMinDrainedPower() { return mMinDrainedPower; } public double getMaxDrainedPower() { return mMaxDrainedPower; } public static byte[] readFully(FileInputStream stream) throws java.io.IOException { return readFully(stream, stream.available()); } public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException { int pos = 0; byte[] data = new byte[avail]; while (true) { int amt = stream.read(data, pos, data.length - pos); //Log.i("foo", "Read " + amt + " bytes at " + pos // + " of avail " + data.length); if (amt <= 0) { //Log.i("foo", "**** FINISHED READING: pos=" + pos // + " len=" + data.length); return data; } pos += amt; avail = stream.available(); 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 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 && sipper.drainType != BatterySipper.DrainType.BLUETOOTH && sipper.drainType != BatterySipper.DrainType.WIFI && sipper.drainType != BatterySipper.DrainType.IDLE) { // 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 sippers, BatterySipper screenSipper) { 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 = getProcessForegroundTimeMs(uid, BatteryStats.STATS_SINCE_CHARGED); activityTimeArray.put(uid.getUid(), timeMs); totalActivityTimeMs += timeMs; } } if (screenSipper != null && 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; } public long convertUsToMs(long timeUs) { return timeUs / 1000; } public long convertMsToUs(long timeMs) { return timeMs * 1000; } @VisibleForTesting public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) { final BatteryStats.Timer timer = uid.getForegroundActivityTimer(); if (timer != null) { return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); } return 0; } @VisibleForTesting public long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) { final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime()); final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP}; long timeUs = 0; for (int type : foregroundTypes) { final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which); timeUs += localTime; } // Return the min value of STATE_TOP time and foreground activity time, since both of these // time have some errors. return convertUsToMs( Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs))); } @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; } mStats = getStats(mBatteryInfo); if (mCollectBatteryBroadcast) { mBatteryBroadcast = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } } private static BatteryStatsImpl getStats(IBatteryStats service) { try { ParcelFileDescriptor pfd = service.getStatisticsStream(); if (pfd != null) { try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor())); Parcel parcel = Parcel.obtain(); parcel.unmarshall(data, 0, data.length); parcel.setDataPosition(0); BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR .createFromParcel(parcel); return stats; } catch (IOException e) { Log.w(TAG, "Unable to read statistics stream", e); } } } catch (RemoteException e) { Log.w(TAG, "RemoteException:", e); } return new BatteryStatsImpl(); } }