BatteryStatsHelper.java revision c3b07a0c9ce9b1a2af644112e678f3963226fad2
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.os;
18
19import static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA;
20import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA;
21import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA;
22import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA;
23
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.hardware.Sensor;
28import android.hardware.SensorManager;
29import android.net.ConnectivityManager;
30import android.os.BatteryStats;
31import android.os.BatteryStats.Uid;
32import android.os.Bundle;
33import android.os.Parcel;
34import android.os.Process;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.os.SystemClock;
38import android.os.UserHandle;
39import android.telephony.SignalStrength;
40import android.util.Log;
41import android.util.SparseArray;
42
43import com.android.internal.app.IBatteryStats;
44import com.android.internal.os.BatterySipper.DrainType;
45
46import java.util.ArrayList;
47import java.util.Collections;
48import java.util.Comparator;
49import java.util.List;
50import java.util.Map;
51import java.util.Arrays;
52
53/**
54 * A helper class for retrieving the power usage information for all applications and services.
55 *
56 * The caller must initialize this class as soon as activity object is ready to use (for example, in
57 * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
58 */
59public class BatteryStatsHelper {
60
61    private static final boolean DEBUG = false;
62
63    private static final String TAG = BatteryStatsHelper.class.getSimpleName();
64
65    private static BatteryStats sStatsXfer;
66    private static Intent sBatteryBroadcastXfer;
67
68    final private Context mContext;
69    final private boolean mCollectBatteryBroadcast;
70
71    private IBatteryStats mBatteryInfo;
72    private BatteryStats mStats;
73    private Intent mBatteryBroadcast;
74    private PowerProfile mPowerProfile;
75
76    private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
77    private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
78    private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
79    private final SparseArray<List<BatterySipper>> mUserSippers
80            = new SparseArray<List<BatterySipper>>();
81    private final SparseArray<Double> mUserPower = new SparseArray<Double>();
82
83    private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>();
84
85    private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
86
87    long mRawRealtime;
88    long mRawUptime;
89    long mBatteryRealtime;
90    long mBatteryUptime;
91    long mTypeBatteryRealtime;
92    long mTypeBatteryUptime;
93    long mBatteryTimeRemaining;
94    long mChargeTimeRemaining;
95
96    private long mStatsPeriod = 0;
97    private double mMaxPower = 1;
98    private double mComputedPower;
99    private double mTotalPower;
100    private double mWifiPower;
101    private double mBluetoothPower;
102    private double mMinDrainedPower;
103    private double mMaxDrainedPower;
104
105    // How much the apps together have kept the mobile radio active.
106    private long mAppMobileActive;
107
108    // How much the apps together have left WIFI running.
109    private long mAppWifiRunning;
110
111    public BatteryStatsHelper(Context context) {
112        this(context, true);
113    }
114
115    public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
116        mContext = context;
117        mCollectBatteryBroadcast = collectBatteryBroadcast;
118    }
119
120    /** Clears the current stats and forces recreating for future use. */
121    public void clearStats() {
122        mStats = null;
123    }
124
125    public BatteryStats getStats() {
126        if (mStats == null) {
127            load();
128        }
129        return mStats;
130    }
131
132    public Intent getBatteryBroadcast() {
133        if (mBatteryBroadcast == null && mCollectBatteryBroadcast) {
134            load();
135        }
136        return mBatteryBroadcast;
137    }
138
139    public PowerProfile getPowerProfile() {
140        return mPowerProfile;
141    }
142
143    public void create(BatteryStats stats) {
144        mPowerProfile = new PowerProfile(mContext);
145        mStats = stats;
146    }
147
148    public void create(Bundle icicle) {
149        if (icicle != null) {
150            mStats = sStatsXfer;
151            mBatteryBroadcast = sBatteryBroadcastXfer;
152        }
153        mBatteryInfo = IBatteryStats.Stub.asInterface(
154                ServiceManager.getService(BatteryStats.SERVICE_NAME));
155        mPowerProfile = new PowerProfile(mContext);
156    }
157
158    public void storeState() {
159        sStatsXfer = mStats;
160        sBatteryBroadcastXfer = mBatteryBroadcast;
161    }
162
163    public static String makemAh(double power) {
164        if (power < .00001) return String.format("%.8f", power);
165        else if (power < .0001) return String.format("%.7f", power);
166        else if (power < .001) return String.format("%.6f", power);
167        else if (power < .01) return String.format("%.5f", power);
168        else if (power < .1) return String.format("%.4f", power);
169        else if (power < 1) return String.format("%.3f", power);
170        else if (power < 10) return String.format("%.2f", power);
171        else if (power < 100) return String.format("%.1f", power);
172        else return String.format("%.0f", power);
173    }
174
175    /**
176     * Refreshes the power usage list.
177     */
178    public void refreshStats(int statsType, int asUser) {
179        SparseArray<UserHandle> users = new SparseArray<UserHandle>(1);
180        users.put(asUser, new UserHandle(asUser));
181        refreshStats(statsType, users);
182    }
183
184    /**
185     * Refreshes the power usage list.
186     */
187    public void refreshStats(int statsType, List<UserHandle> asUsers) {
188        final int n = asUsers.size();
189        SparseArray<UserHandle> users = new SparseArray<UserHandle>(n);
190        for (int i = 0; i < n; ++i) {
191            UserHandle userHandle = asUsers.get(i);
192            users.put(userHandle.getIdentifier(), userHandle);
193        }
194        refreshStats(statsType, users);
195    }
196
197    /**
198     * Refreshes the power usage list.
199     */
200    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
201        refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
202                SystemClock.uptimeMillis() * 1000);
203    }
204
205    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
206            long rawUptimeUs) {
207        // Initialize mStats if necessary.
208        getStats();
209
210        mMaxPower = 0;
211        mComputedPower = 0;
212        mTotalPower = 0;
213        mWifiPower = 0;
214        mBluetoothPower = 0;
215        mAppMobileActive = 0;
216        mAppWifiRunning = 0;
217
218        mUsageList.clear();
219        mWifiSippers.clear();
220        mBluetoothSippers.clear();
221        mUserSippers.clear();
222        mUserPower.clear();
223        mMobilemsppList.clear();
224
225        if (mStats == null) {
226            return;
227        }
228
229        mStatsType = statsType;
230        mRawUptime = rawUptimeUs;
231        mRawRealtime = rawRealtimeUs;
232        mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs);
233        mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs);
234        mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
235        mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
236        mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
237        mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs);
238
239        if (DEBUG) {
240            Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime="
241                    + (rawUptimeUs/1000));
242            Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
243                    + (mBatteryUptime/1000));
244            Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
245                    + (mTypeBatteryUptime/1000));
246        }
247        mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
248                * mPowerProfile.getBatteryCapacity()) / 100;
249        mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
250                * mPowerProfile.getBatteryCapacity()) / 100;
251
252        processAppUsage(asUsers);
253
254        // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
255        for (int i=0; i<mUsageList.size(); i++) {
256            BatterySipper bs = mUsageList.get(i);
257            bs.computeMobilemspp();
258            if (bs.mobilemspp != 0) {
259                mMobilemsppList.add(bs);
260            }
261        }
262        for (int i=0; i<mUserSippers.size(); i++) {
263            List<BatterySipper> user = mUserSippers.valueAt(i);
264            for (int j=0; j<user.size(); j++) {
265                BatterySipper bs = user.get(j);
266                bs.computeMobilemspp();
267                if (bs.mobilemspp != 0) {
268                    mMobilemsppList.add(bs);
269                }
270            }
271        }
272        Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
273            @Override
274            public int compare(BatterySipper lhs, BatterySipper rhs) {
275                if (lhs.mobilemspp < rhs.mobilemspp) {
276                    return 1;
277                } else if (lhs.mobilemspp > rhs.mobilemspp) {
278                    return -1;
279                }
280                return 0;
281            }
282        });
283
284        processMiscUsage();
285
286        if (DEBUG) {
287            Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
288                    + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
289        }
290        mTotalPower = mComputedPower;
291        if (mStats.getLowDischargeAmountSinceCharge() > 1) {
292            if (mMinDrainedPower > mComputedPower) {
293                double amount = mMinDrainedPower - mComputedPower;
294                mTotalPower = mMinDrainedPower;
295                addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount);
296            } else if (mMaxDrainedPower < mComputedPower) {
297                double amount = mComputedPower - mMaxDrainedPower;
298                addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount);
299            }
300        }
301
302        Collections.sort(mUsageList);
303    }
304
305    private void processAppUsage(SparseArray<UserHandle> asUsers) {
306        final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
307        SensorManager sensorManager = (SensorManager) mContext.getSystemService(
308                Context.SENSOR_SERVICE);
309        final int which = mStatsType;
310        final int speedSteps = mPowerProfile.getNumSpeedSteps();
311        final double[] powerCpuNormal = new double[speedSteps];
312        final long[] cpuSpeedStepTimes = new long[speedSteps];
313        for (int p = 0; p < speedSteps; p++) {
314            powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
315        }
316        final double mobilePowerPerPacket = getMobilePowerPerPacket();
317        final double mobilePowerPerMs = getMobilePowerPerMs();
318        final double wifiPowerPerPacket = getWifiPowerPerPacket();
319        long appWakelockTimeUs = 0;
320        BatterySipper osApp = null;
321        mStatsPeriod = mTypeBatteryRealtime;
322        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
323        final int NU = uidStats.size();
324        for (int iu = 0; iu < NU; iu++) {
325            Uid u = uidStats.valueAt(iu);
326            double p; // in mAs
327            double power = 0; // in mAs
328            double highestDrain = 0;
329            String packageWithHighestDrain = null;
330            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
331            long cpuTime = 0;
332            long cpuFgTime = 0;
333            long wakelockTime = 0;
334            long gpsTime = 0;
335            if (processStats.size() > 0) {
336                // Process CPU time
337                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
338                        : processStats.entrySet()) {
339                    Uid.Proc ps = ent.getValue();
340                    final long userTime = ps.getUserTime(which);
341                    final long systemTime = ps.getSystemTime(which);
342                    final long foregroundTime = ps.getForegroundTime(which);
343                    cpuFgTime += foregroundTime * 10; // convert to millis
344                    final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
345                    int totalTimeAtSpeeds = 0;
346                    // Get the total first
347                    for (int step = 0; step < speedSteps; step++) {
348                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
349                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
350                    }
351                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
352                    // Then compute the ratio of time spent at each speed
353                    double processPower = 0;
354                    for (int step = 0; step < speedSteps; step++) {
355                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
356                        if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
357                                + step + " ratio=" + makemAh(ratio) + " power="
358                                + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));
359                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
360                    }
361                    cpuTime += tmpCpuTime;
362                    if (DEBUG && processPower != 0) {
363                        Log.d(TAG, String.format("process %s, cpu power=%s",
364                                ent.getKey(), makemAh(processPower / (60*60*1000))));
365                    }
366                    power += processPower;
367                    if (packageWithHighestDrain == null
368                            || packageWithHighestDrain.startsWith("*")) {
369                        highestDrain = processPower;
370                        packageWithHighestDrain = ent.getKey();
371                    } else if (highestDrain < processPower
372                            && !ent.getKey().startsWith("*")) {
373                        highestDrain = processPower;
374                        packageWithHighestDrain = ent.getKey();
375                    }
376                }
377            }
378            if (cpuFgTime > cpuTime) {
379                if (DEBUG && cpuFgTime > cpuTime + 10000) {
380                    Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
381                }
382                cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
383            }
384            power /= (60*60*1000);
385
386            // Process wake lock usage
387            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
388            for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
389                    : wakelockStats.entrySet()) {
390                Uid.Wakelock wakelock = wakelockEntry.getValue();
391                // Only care about partial wake locks since full wake locks
392                // are canceled when the user turns the screen off.
393                BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
394                if (timer != null) {
395                    wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which);
396                }
397            }
398            appWakelockTimeUs += wakelockTime;
399            wakelockTime /= 1000; // convert to millis
400
401            // Add cost of holding a wake lock
402            p = (wakelockTime
403                    * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
404            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
405                    + wakelockTime + " power=" + makemAh(p));
406            power += p;
407
408            // Add cost of mobile traffic
409            final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
410            final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
411            final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
412            final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
413            final long mobileActive = u.getMobileRadioActiveTime(mStatsType);
414            if (mobileActive > 0) {
415                // We are tracking when the radio is up, so can use the active time to
416                // determine power use.
417                mAppMobileActive += mobileActive;
418                p = (mobilePowerPerMs * mobileActive) / 1000;
419            } else {
420                // We are not tracking when the radio is up, so must approximate power use
421                // based on the number of packets.
422                p = (mobileRx + mobileTx) * mobilePowerPerPacket;
423            }
424            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
425                    + (mobileRx+mobileTx) + " active time " + mobileActive
426                    + " power=" + makemAh(p));
427            power += p;
428
429            // Add cost of wifi traffic
430            final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
431            final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
432            final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
433            final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
434            p = (wifiRx + wifiTx) * wifiPowerPerPacket;
435            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
436                    + (mobileRx+mobileTx) + " power=" + makemAh(p));
437            power += p;
438
439            // Add cost of keeping WIFI running.
440            long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000;
441            mAppWifiRunning += wifiRunningTimeMs;
442            p = (wifiRunningTimeMs
443                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
444            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
445                    + wifiRunningTimeMs + " power=" + makemAh(p));
446            power += p;
447
448            // Add cost of WIFI scans
449            long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
450            p = (wifiScanTimeMs
451                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
452            if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
453                    + " power=" + makemAh(p));
454            power += p;
455            for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
456                long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
457                p = ((batchScanTimeMs
458                        * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
459                    ) / (60*60*1000);
460                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
461                        + " time=" + batchScanTimeMs + " power=" + makemAh(p));
462                power += p;
463            }
464
465            // Process Sensor usage
466            SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
467            int NSE = sensorStats.size();
468            for (int ise=0; ise<NSE; ise++) {
469                Uid.Sensor sensor = sensorStats.valueAt(ise);
470                int sensorHandle = sensorStats.keyAt(ise);
471                BatteryStats.Timer timer = sensor.getSensorTime();
472                long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
473                double multiplier = 0;
474                switch (sensorHandle) {
475                    case Uid.Sensor.GPS:
476                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
477                        gpsTime = sensorTime;
478                        break;
479                    default:
480                        List<Sensor> sensorList = sensorManager.getSensorList(
481                                android.hardware.Sensor.TYPE_ALL);
482                        for (android.hardware.Sensor s : sensorList) {
483                            if (s.getHandle() == sensorHandle) {
484                                multiplier = s.getPower();
485                                break;
486                            }
487                        }
488                }
489                p = (multiplier * sensorTime) / (60*60*1000);
490                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
491                        + " time=" + sensorTime + " power=" + makemAh(p));
492                power += p;
493            }
494
495            if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
496                    u.getUid(), makemAh(power)));
497
498            // Add the app to the list if it is consuming power
499            final int userId = UserHandle.getUserId(u.getUid());
500            if (power != 0 || u.getUid() == 0) {
501                BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
502                        new double[] {power});
503                app.cpuTime = cpuTime;
504                app.gpsTime = gpsTime;
505                app.wifiRunningTime = wifiRunningTimeMs;
506                app.cpuFgTime = cpuFgTime;
507                app.wakeLockTime = wakelockTime;
508                app.mobileRxPackets = mobileRx;
509                app.mobileTxPackets = mobileTx;
510                app.mobileActive = mobileActive / 1000;
511                app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
512                app.wifiRxPackets = wifiRx;
513                app.wifiTxPackets = wifiTx;
514                app.mobileRxBytes = mobileRxB;
515                app.mobileTxBytes = mobileTxB;
516                app.wifiRxBytes = wifiRxB;
517                app.wifiTxBytes = wifiTxB;
518                app.packageWithHighestDrain = packageWithHighestDrain;
519                if (u.getUid() == Process.WIFI_UID) {
520                    mWifiSippers.add(app);
521                    mWifiPower += power;
522                } else if (u.getUid() == Process.BLUETOOTH_UID) {
523                    mBluetoothSippers.add(app);
524                    mBluetoothPower += power;
525                } else if (!forAllUsers && asUsers.get(userId) == null
526                        && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
527                    List<BatterySipper> list = mUserSippers.get(userId);
528                    if (list == null) {
529                        list = new ArrayList<BatterySipper>();
530                        mUserSippers.put(userId, list);
531                    }
532                    list.add(app);
533                    if (power != 0) {
534                        Double userPower = mUserPower.get(userId);
535                        if (userPower == null) {
536                            userPower = power;
537                        } else {
538                            userPower += power;
539                        }
540                        mUserPower.put(userId, userPower);
541                    }
542                } else {
543                    mUsageList.add(app);
544                    if (power > mMaxPower) mMaxPower = power;
545                    mComputedPower += power;
546                }
547                if (u.getUid() == 0) {
548                    osApp = app;
549                }
550            }
551        }
552
553        // The device has probably been awake for longer than the screen on
554        // time and application wake lock time would account for.  Assign
555        // this remainder to the OS, if possible.
556        if (osApp != null) {
557            long wakeTimeMillis = mBatteryUptime / 1000;
558            wakeTimeMillis -= (appWakelockTimeUs / 1000)
559                    + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);
560            if (wakeTimeMillis > 0) {
561                double power = (wakeTimeMillis
562                        * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
563                        /  (60*60*1000);
564                if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
565                        + makemAh(power));
566                osApp.wakeLockTime += wakeTimeMillis;
567                osApp.value += power;
568                osApp.values[0] += power;
569                if (osApp.value > mMaxPower) mMaxPower = osApp.value;
570                mComputedPower += power;
571            }
572        }
573    }
574
575    private void addPhoneUsage() {
576        long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000;
577        double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
578                * phoneOnTimeMs / (60*60*1000);
579        if (phoneOnPower != 0) {
580            BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
581        }
582    }
583
584    private void addScreenUsage() {
585        double power = 0;
586        long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtime, mStatsType) / 1000;
587        power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
588        final double screenFullPower =
589                mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
590        for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
591            double screenBinPower = screenFullPower * (i + 0.5f)
592                    / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
593            long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtime, mStatsType)
594                    / 1000;
595            double p = screenBinPower*brightnessTime;
596            if (DEBUG && p != 0) {
597                Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
598                        + " power=" + makemAh(p / (60 * 60 * 1000)));
599            }
600            power += p;
601        }
602        power /= (60*60*1000); // To hours
603        if (power != 0) {
604            addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
605        }
606    }
607
608    private void addRadioUsage() {
609        double power = 0;
610        final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
611        long signalTimeMs = 0;
612        long noCoverageTimeMs = 0;
613        for (int i = 0; i < BINS; i++) {
614            long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mRawRealtime, mStatsType)
615                    / 1000;
616            double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i))
617                        / (60*60*1000);
618            if (DEBUG && p != 0) {
619                Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
620                        + makemAh(p));
621            }
622            power += p;
623            signalTimeMs += strengthTimeMs;
624            if (i == 0) {
625                noCoverageTimeMs = strengthTimeMs;
626            }
627        }
628        long scanningTimeMs = mStats.getPhoneSignalScanningTime(mRawRealtime, mStatsType)
629                / 1000;
630        double p = (scanningTimeMs * mPowerProfile.getAveragePower(
631                        PowerProfile.POWER_RADIO_SCANNING))
632                        / (60*60*1000);
633        if (DEBUG && p != 0) {
634            Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p));
635        }
636        power += p;
637        long radioActiveTimeUs = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType);
638        long remainingActiveTime = (radioActiveTimeUs - mAppMobileActive) / 1000;
639        if (remainingActiveTime > 0) {
640            power += getMobilePowerPerMs() * remainingActiveTime;
641        }
642        if (power != 0) {
643            BatterySipper bs =
644                    addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power);
645            if (signalTimeMs != 0) {
646                bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
647            }
648            bs.mobileActive = remainingActiveTime;
649            bs.mobileActiveCount = mStats.getMobileRadioActiveUnknownCount(mStatsType);
650        }
651    }
652
653    private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
654        for (int i=0; i<from.size(); i++) {
655            BatterySipper wbs = from.get(i);
656            if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
657            bs.cpuTime += wbs.cpuTime;
658            bs.gpsTime += wbs.gpsTime;
659            bs.wifiRunningTime += wbs.wifiRunningTime;
660            bs.cpuFgTime += wbs.cpuFgTime;
661            bs.wakeLockTime += wbs.wakeLockTime;
662            bs.mobileRxPackets += wbs.mobileRxPackets;
663            bs.mobileTxPackets += wbs.mobileTxPackets;
664            bs.mobileActive += wbs.mobileActive;
665            bs.mobileActiveCount += wbs.mobileActiveCount;
666            bs.wifiRxPackets += wbs.wifiRxPackets;
667            bs.wifiTxPackets += wbs.wifiTxPackets;
668            bs.mobileRxBytes += wbs.mobileRxBytes;
669            bs.mobileTxBytes += wbs.mobileTxBytes;
670            bs.wifiRxBytes += wbs.wifiRxBytes;
671            bs.wifiTxBytes += wbs.wifiTxBytes;
672        }
673        bs.computeMobilemspp();
674    }
675
676    private void addWiFiUsage() {
677        long onTimeMs = mStats.getWifiOnTime(mRawRealtime, mStatsType) / 1000;
678        long runningTimeMs = mStats.getGlobalWifiRunningTime(mRawRealtime, mStatsType) / 1000;
679        if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs
680                + " app runningTime=" + mAppWifiRunning);
681        runningTimeMs -= mAppWifiRunning;
682        if (runningTimeMs < 0) runningTimeMs = 0;
683        double wifiPower = (onTimeMs * 0 /* TODO */
684                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
685                + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON))
686                / (60*60*1000);
687        if (DEBUG && wifiPower != 0) {
688            Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower));
689        }
690        if ((wifiPower+mWifiPower) != 0) {
691            BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs,
692                    wifiPower + mWifiPower);
693            aggregateSippers(bs, mWifiSippers, "WIFI");
694        }
695    }
696
697    private void addIdleUsage() {
698        long idleTimeMs = (mTypeBatteryRealtime
699                - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000;
700        double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
701                / (60*60*1000);
702        if (DEBUG && idlePower != 0) {
703            Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower));
704        }
705        if (idlePower != 0) {
706            addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower);
707        }
708    }
709
710    private void addBluetoothUsage() {
711        long btOnTimeMs = mStats.getBluetoothOnTime(mRawRealtime, mStatsType) / 1000;
712        double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
713                / (60*60*1000);
714        if (DEBUG && btPower != 0) {
715            Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower));
716        }
717        int btPingCount = mStats.getBluetoothPingCount();
718        double pingPower = (btPingCount
719                * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD))
720                / (60*60*1000);
721        if (DEBUG && pingPower != 0) {
722            Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower));
723        }
724        btPower += pingPower;
725        if ((btPower+mBluetoothPower) != 0) {
726            BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs,
727                    btPower + mBluetoothPower);
728            aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
729        }
730    }
731
732    private void addFlashlightUsage() {
733        long flashlightOnTimeMs = mStats.getFlashlightOnTime(mRawRealtime, mStatsType) / 1000;
734        double flashlightPower = flashlightOnTimeMs
735                * mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT) / (60*60*1000);
736        if (flashlightPower != 0) {
737            addEntry(BatterySipper.DrainType.FLASHLIGHT, flashlightOnTimeMs, flashlightPower);
738        }
739    }
740
741    private void addUserUsage() {
742        for (int i=0; i<mUserSippers.size(); i++) {
743            final int userId = mUserSippers.keyAt(i);
744            final List<BatterySipper> sippers = mUserSippers.valueAt(i);
745            Double userPower = mUserPower.get(userId);
746            double power = (userPower != null) ? userPower : 0.0;
747            BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power);
748            bs.userId = userId;
749            aggregateSippers(bs, sippers, "User");
750        }
751    }
752
753    /**
754     * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
755     */
756    private double getMobilePowerPerPacket() {
757        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
758        final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
759                / 3600;
760
761        final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
762        final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
763        final long mobileData = mobileRx + mobileTx;
764
765        final long radioDataUptimeMs
766                = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType) / 1000;
767        final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
768                ? (mobileData / (double)radioDataUptimeMs)
769                : (((double)MOBILE_BPS) / 8 / 2048);
770
771        return (MOBILE_POWER / mobilePps) / (60*60);
772    }
773
774    /**
775     * Return estimated power (in mAs) of keeping the radio up
776     */
777    private double getMobilePowerPerMs() {
778        return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) / (60*60*1000);
779    }
780
781    /**
782     * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
783     */
784    private double getWifiPowerPerPacket() {
785        final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
786        final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
787                / 3600;
788        return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
789    }
790
791    private void processMiscUsage() {
792        addUserUsage();
793        addPhoneUsage();
794        addScreenUsage();
795        addFlashlightUsage();
796        addWiFiUsage();
797        addBluetoothUsage();
798        addIdleUsage(); // Not including cellular idle power
799        // Don't compute radio usage if it's a wifi-only device
800        ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
801                Context.CONNECTIVITY_SERVICE);
802        if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
803            addRadioUsage();
804        }
805    }
806
807    private BatterySipper addEntry(DrainType drainType, long time, double power) {
808        mComputedPower += power;
809        return addEntryNoTotal(drainType, time, power);
810    }
811
812    private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) {
813        if (power > mMaxPower) mMaxPower = power;
814        BatterySipper bs = new BatterySipper(drainType, null, new double[] {power});
815        bs.usageTime = time;
816        mUsageList.add(bs);
817        return bs;
818    }
819
820    public List<BatterySipper> getUsageList() {
821        return mUsageList;
822    }
823
824    public List<BatterySipper> getMobilemsppList() {
825        return mMobilemsppList;
826    }
827
828    public long getStatsPeriod() { return mStatsPeriod; }
829
830    public int getStatsType() { return mStatsType; };
831
832    public double getMaxPower() { return mMaxPower; }
833
834    public double getTotalPower() { return mTotalPower; }
835
836    public double getComputedPower() { return mComputedPower; }
837
838    public double getMinDrainedPower() {
839        return mMinDrainedPower;
840    }
841
842    public double getMaxDrainedPower() {
843        return mMaxDrainedPower;
844    }
845
846    public long getBatteryTimeRemaining() { return mBatteryTimeRemaining; }
847
848    public long getChargeTimeRemaining() { return mChargeTimeRemaining; }
849
850    private void load() {
851        if (mBatteryInfo == null) {
852            return;
853        }
854        try {
855            byte[] data = mBatteryInfo.getStatistics();
856            Parcel parcel = Parcel.obtain();
857            parcel.unmarshall(data, 0, data.length);
858            parcel.setDataPosition(0);
859            BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
860                    .createFromParcel(parcel);
861            stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
862            mStats = stats;
863        } catch (RemoteException e) {
864            Log.e(TAG, "RemoteException:", e);
865        }
866        if (mCollectBatteryBroadcast) {
867            mBatteryBroadcast = mContext.registerReceiver(null,
868                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
869        }
870    }
871}
872