Import Android SDK Platform PI [4335822]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4335822 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4335822.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
diff --git a/com/android/internal/os/BatteryStatsHelper.java b/com/android/internal/os/BatteryStatsHelper.java
new file mode 100644
index 0000000..f085e29
--- /dev/null
+++ b/com/android/internal/os/BatteryStatsHelper.java
@@ -0,0 +1,1024 @@
+/*
+ * 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<File, BatteryStats> 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<BatterySipper> mUsageList = new ArrayList<>();
+
+    /**
+     * List of apps using wifi power.
+     */
+    private final List<BatterySipper> mWifiSippers = new ArrayList<>();
+
+    /**
+     * List of apps using bluetooth power.
+     */
+    private final List<BatterySipper> mBluetoothSippers = new ArrayList<>();
+
+    private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>();
+
+    private final List<BatterySipper> 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<UserHandle> users = new SparseArray<>(1);
+        users.put(asUser, new UserHandle(asUser));
+        refreshStats(statsType, users);
+    }
+
+    /**
+     * Refreshes the power usage list.
+     */
+    public void refreshStats(int statsType, List<UserHandle> asUsers) {
+        final int n = asUsers.size();
+        SparseArray<UserHandle> 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<UserHandle> asUsers) {
+        refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
+                SystemClock.uptimeMillis() * 1000);
+    }
+
+    public void refreshStats(int statsType, SparseArray<UserHandle> 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<BatterySipper> 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<BatterySipper>() {
+            @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<UserHandle> asUsers) {
+        final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
+        mStatsPeriod = mTypeBatteryRealtimeUs;
+
+        BatterySipper osSipper = null;
+        final SparseArray<? extends Uid> 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<BatterySipper> 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<BatterySipper> 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<BatterySipper> getUsageList() {
+        return mUsageList;
+    }
+
+    public List<BatterySipper> 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<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
+                        && 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<BatterySipper> 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();
+    }
+}