BatteryStatsHelper.java revision ae19a06e030e55b4db3cb20f1e564d49a78a395e
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.hardware.Sensor;
26import android.hardware.SensorManager;
27import android.net.ConnectivityManager;
28import android.os.BatteryStats;
29import android.os.BatteryStats.Uid;
30import android.os.Bundle;
31import android.os.Parcel;
32import android.os.Process;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.SystemClock;
36import android.os.UserHandle;
37import android.telephony.SignalStrength;
38import android.util.Log;
39import android.util.SparseArray;
40
41import com.android.internal.app.IBatteryStats;
42import com.android.internal.os.BatterySipper.DrainType;
43
44import java.io.PrintWriter;
45import java.util.ArrayList;
46import java.util.Collections;
47import java.util.List;
48import java.util.Map;
49
50/**
51 * A helper class for retrieving the power usage information for all applications and services.
52 *
53 * The caller must initialize this class as soon as activity object is ready to use (for example, in
54 * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
55 */
56public class BatteryStatsHelper {
57
58    private static final boolean DEBUG = true;
59
60    private static final String TAG = BatteryStatsHelper.class.getSimpleName();
61
62    private static BatteryStats sStatsXfer;
63
64    final private Context mContext;
65
66    private IBatteryStats mBatteryInfo;
67    private BatteryStats mStats;
68    private PowerProfile mPowerProfile;
69
70    private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
71    private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
72    private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
73    private final SparseArray<List<BatterySipper>> mUserSippers
74            = new SparseArray<List<BatterySipper>>();
75    private final SparseArray<Double> mUserPower = new SparseArray<Double>();
76
77    private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
78    private int mAsUser = 0;
79
80    long mBatteryRealtime;
81    long mBatteryUptime;
82    long mTypeBatteryRealtime;
83    long mTypeBatteryUptime;
84
85    private long mStatsPeriod = 0;
86    private double mMaxPower = 1;
87    private double mTotalPower;
88    private double mWifiPower;
89    private double mBluetoothPower;
90    private double mMinDrainedPower;
91    private double mMaxDrainedPower;
92
93    // How much the apps together have left WIFI running.
94    private long mAppWifiRunning;
95
96    public BatteryStatsHelper(Context context) {
97        mContext = context;
98    }
99
100    /** Clears the current stats and forces recreating for future use. */
101    public void clearStats() {
102        mStats = null;
103    }
104
105    public BatteryStats getStats() {
106        if (mStats == null) {
107            load();
108        }
109        return mStats;
110    }
111
112    public PowerProfile getPowerProfile() {
113        return mPowerProfile;
114    }
115
116    public void create(BatteryStats stats) {
117        mPowerProfile = new PowerProfile(mContext);
118        mStats = stats;
119    }
120
121    public void create(Bundle icicle) {
122        if (icicle != null) {
123            mStats = sStatsXfer;
124        }
125        mBatteryInfo = IBatteryStats.Stub.asInterface(
126                ServiceManager.getService(BatteryStats.SERVICE_NAME));
127        mPowerProfile = new PowerProfile(mContext);
128    }
129
130    public void storeState() {
131        sStatsXfer = mStats;
132    }
133
134    public static String makemAh(double power) {
135        if (power < .0001) return String.format("%.8f", power);
136        else if (power < .0001) return String.format("%.7f", power);
137        else if (power < .001) return String.format("%.6f", power);
138        else if (power < .01) return String.format("%.5f", power);
139        else if (power < .1) return String.format("%.4f", power);
140        else if (power < 1) return String.format("%.3f", power);
141        else if (power < 10) return String.format("%.2f", power);
142        else if (power < 100) return String.format("%.1f", power);
143        else return String.format("%.0f", power);
144    }
145
146    /**
147     * Refreshes the power usage list.
148     */
149    public void refreshStats(int statsType, int asUser) {
150        refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000,
151                SystemClock.uptimeMillis() * 1000);
152    }
153
154    public void refreshStats(int statsType, int asUser, long rawRealtimeNano, long rawUptimeNano) {
155        // Initialize mStats if necessary.
156        getStats();
157
158        mMaxPower = 0;
159        mTotalPower = 0;
160        mWifiPower = 0;
161        mBluetoothPower = 0;
162        mAppWifiRunning = 0;
163
164        mUsageList.clear();
165        mWifiSippers.clear();
166        mBluetoothSippers.clear();
167        mUserSippers.clear();
168        mUserPower.clear();
169
170        if (mStats == null) {
171            return;
172        }
173
174        mStatsType = statsType;
175        mAsUser = asUser;
176        mBatteryUptime = mStats.getBatteryUptime(rawUptimeNano);
177        mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeNano);
178        mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeNano, mStatsType);
179        mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeNano, mStatsType);
180
181        if (DEBUG) {
182            Log.d(TAG, "Raw time: realtime=" + (rawRealtimeNano/1000) + " uptime="
183                    + (rawUptimeNano/1000));
184            Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
185                    + (mBatteryUptime/1000));
186            Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
187                    + (mTypeBatteryUptime/1000));
188        }
189        mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
190                * mPowerProfile.getBatteryCapacity()) / 100;
191        mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
192                * mPowerProfile.getBatteryCapacity()) / 100;
193
194        processAppUsage();
195        processMiscUsage();
196
197        if (DEBUG) {
198            Log.d(TAG, "Accuracy: total computed=" + makemAh(mTotalPower) + ", min discharge="
199                    + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
200        }
201        if (true || mStats.getLowDischargeAmountSinceCharge() > 10) {
202            if (mMinDrainedPower > mTotalPower) {
203                double amount = mMinDrainedPower - mTotalPower;
204                if (mMaxPower < amount) {
205                    mMaxPower = amount;
206                }
207                addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount);
208            } else if (mMaxDrainedPower < mTotalPower) {
209                double amount = mTotalPower - mMaxDrainedPower;
210                if (mMaxPower < amount) {
211                    mMaxPower = amount;
212                }
213                addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount);
214            }
215        }
216
217        Collections.sort(mUsageList);
218    }
219
220    private void processAppUsage() {
221        SensorManager sensorManager = (SensorManager) mContext.getSystemService(
222                Context.SENSOR_SERVICE);
223        final int which = mStatsType;
224        final int speedSteps = mPowerProfile.getNumSpeedSteps();
225        final double[] powerCpuNormal = new double[speedSteps];
226        final long[] cpuSpeedStepTimes = new long[speedSteps];
227        for (int p = 0; p < speedSteps; p++) {
228            powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
229        }
230        final double mobilePowerPerPacket = getMobilePowerPerPacket();
231        final double wifiPowerPerPacket = getWifiPowerPerPacket();
232        long appWakelockTime = 0;
233        BatterySipper osApp = null;
234        mStatsPeriod = mTypeBatteryRealtime;
235        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
236        final int NU = uidStats.size();
237        for (int iu = 0; iu < NU; iu++) {
238            Uid u = uidStats.valueAt(iu);
239            double p; // in mAs
240            double power = 0; // in mAs
241            double highestDrain = 0;
242            String packageWithHighestDrain = null;
243            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
244            long cpuTime = 0;
245            long cpuFgTime = 0;
246            long wakelockTime = 0;
247            long gpsTime = 0;
248            if (processStats.size() > 0) {
249                // Process CPU time
250                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
251                        : processStats.entrySet()) {
252                    Uid.Proc ps = ent.getValue();
253                    final long userTime = ps.getUserTime(which);
254                    final long systemTime = ps.getSystemTime(which);
255                    final long foregroundTime = ps.getForegroundTime(which);
256                    cpuFgTime += foregroundTime * 10; // convert to millis
257                    final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
258                    int totalTimeAtSpeeds = 0;
259                    // Get the total first
260                    for (int step = 0; step < speedSteps; step++) {
261                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
262                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
263                    }
264                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
265                    // Then compute the ratio of time spent at each speed
266                    double processPower = 0;
267                    for (int step = 0; step < speedSteps; step++) {
268                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
269                        if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
270                                + step + " ratio=" + makemAh(ratio) + " power="
271                                + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));
272                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
273                    }
274                    cpuTime += tmpCpuTime;
275                    if (DEBUG && processPower != 0) {
276                        Log.d(TAG, String.format("process %s, cpu power=%s",
277                                ent.getKey(), makemAh(processPower / (60*60*1000))));
278                    }
279                    power += processPower;
280                    if (packageWithHighestDrain == null
281                            || packageWithHighestDrain.startsWith("*")) {
282                        highestDrain = processPower;
283                        packageWithHighestDrain = ent.getKey();
284                    } else if (highestDrain < processPower
285                            && !ent.getKey().startsWith("*")) {
286                        highestDrain = processPower;
287                        packageWithHighestDrain = ent.getKey();
288                    }
289                }
290            }
291            if (cpuFgTime > cpuTime) {
292                if (DEBUG && cpuFgTime > cpuTime + 10000) {
293                    Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
294                }
295                cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
296            }
297            power /= (60*60*1000);
298
299            // Process wake lock usage
300            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
301            for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
302                    : wakelockStats.entrySet()) {
303                Uid.Wakelock wakelock = wakelockEntry.getValue();
304                // Only care about partial wake locks since full wake locks
305                // are canceled when the user turns the screen off.
306                BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
307                if (timer != null) {
308                    wakelockTime += timer.getTotalTimeLocked(mBatteryRealtime, which);
309                }
310            }
311            wakelockTime /= 1000; // convert to millis
312            appWakelockTime += wakelockTime;
313
314            // Add cost of holding a wake lock
315            p = (wakelockTime
316                    * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
317            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
318                    + wakelockTime + " power=" + makemAh(p));
319            power += p;
320
321            // Add cost of mobile traffic
322            final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
323            final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
324            final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
325            final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
326            p = (mobileRx + mobileTx) * mobilePowerPerPacket;
327            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
328                    + (mobileRx+mobileTx) + " power=" + makemAh(p));
329            power += p;
330
331            // Add cost of wifi traffic
332            final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
333            final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
334            final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
335            final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
336            p = (wifiRx + wifiTx) * wifiPowerPerPacket;
337            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
338                    + (mobileRx+mobileTx) + " power=" + makemAh(p));
339            power += p;
340
341            // Add cost of keeping WIFI running.
342            long wifiRunningTimeMs = u.getWifiRunningTime(mBatteryRealtime, which) / 1000;
343            mAppWifiRunning += wifiRunningTimeMs;
344            p = (wifiRunningTimeMs
345                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
346            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
347                    + wifiRunningTimeMs + " power=" + makemAh(p));
348            power += p;
349
350            // Add cost of WIFI scans
351            long wifiScanTimeMs = u.getWifiScanTime(mBatteryRealtime, which) / 1000;
352            p = (wifiScanTimeMs
353                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
354            if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
355                    + " power=" + makemAh(p));
356            power += p;
357            for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
358                long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mBatteryRealtime, which) / 1000;
359                p = ((batchScanTimeMs
360                        * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
361                    ) / (60*60*1000);
362                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
363                        + " time=" + batchScanTimeMs + " power=" + makemAh(p));
364                power += p;
365            }
366
367            // Process Sensor usage
368            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
369            for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
370                    : sensorStats.entrySet()) {
371                Uid.Sensor sensor = sensorEntry.getValue();
372                int sensorHandle = sensor.getHandle();
373                BatteryStats.Timer timer = sensor.getSensorTime();
374                long sensorTime = timer.getTotalTimeLocked(mBatteryRealtime, which) / 1000;
375                double multiplier = 0;
376                switch (sensorHandle) {
377                    case Uid.Sensor.GPS:
378                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
379                        gpsTime = sensorTime;
380                        break;
381                    default:
382                        List<Sensor> sensorList = sensorManager.getSensorList(
383                                android.hardware.Sensor.TYPE_ALL);
384                        for (android.hardware.Sensor s : sensorList) {
385                            if (s.getHandle() == sensorHandle) {
386                                multiplier = s.getPower();
387                                break;
388                            }
389                        }
390                }
391                p = (multiplier * sensorTime) / (60*60*1000);
392                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
393                        + " time=" + sensorTime + " power=" + makemAh(p));
394                power += p;
395            }
396
397            if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
398                    u.getUid(), makemAh(power)));
399
400            // Add the app to the list if it is consuming power
401            final int userId = UserHandle.getUserId(u.getUid());
402            if (power != 0 || u.getUid() == 0) {
403                BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
404                        new double[] {power});
405                app.cpuTime = cpuTime;
406                app.gpsTime = gpsTime;
407                app.wifiRunningTime = wifiRunningTimeMs;
408                app.cpuFgTime = cpuFgTime;
409                app.wakeLockTime = wakelockTime;
410                app.mobileRxPackets = mobileRx;
411                app.mobileTxPackets = mobileTx;
412                app.wifiRxPackets = wifiRx;
413                app.wifiTxPackets = wifiTx;
414                app.mobileRxBytes = mobileRxB;
415                app.mobileTxBytes = mobileTxB;
416                app.wifiRxBytes = wifiRxB;
417                app.wifiTxBytes = wifiTxB;
418                app.packageWithHighestDrain = packageWithHighestDrain;
419                if (u.getUid() == Process.WIFI_UID) {
420                    mWifiSippers.add(app);
421                    mWifiPower += power;
422                } else if (u.getUid() == Process.BLUETOOTH_UID) {
423                    mBluetoothSippers.add(app);
424                    mBluetoothPower += power;
425                } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser
426                        && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
427                    List<BatterySipper> list = mUserSippers.get(userId);
428                    if (list == null) {
429                        list = new ArrayList<BatterySipper>();
430                        mUserSippers.put(userId, list);
431                    }
432                    list.add(app);
433                    if (power != 0) {
434                        Double userPower = mUserPower.get(userId);
435                        if (userPower == null) {
436                            userPower = power;
437                        } else {
438                            userPower += power;
439                        }
440                        mUserPower.put(userId, userPower);
441                    }
442                } else {
443                    mUsageList.add(app);
444                    if (power > mMaxPower) mMaxPower = power;
445                    mTotalPower += power;
446                }
447                if (u.getUid() == 0) {
448                    osApp = app;
449                }
450            }
451        }
452
453        // The device has probably been awake for longer than the screen on
454        // time and application wake lock time would account for.  Assign
455        // this remainder to the OS, if possible.
456        if (osApp != null) {
457            long wakeTimeMillis = mBatteryUptime / 1000;
458            wakeTimeMillis -= appWakelockTime
459                    + (mStats.getScreenOnTime(mBatteryRealtime, which) / 1000);
460            if (wakeTimeMillis > 0) {
461                double power = (wakeTimeMillis
462                        * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
463                        /  (60*60*1000);
464                if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
465                        + makemAh(power));
466                osApp.wakeLockTime += wakeTimeMillis;
467                osApp.value += power;
468                osApp.values[0] += power;
469                if (osApp.value > mMaxPower) mMaxPower = osApp.value;
470                mTotalPower += power;
471            }
472        }
473    }
474
475    private void addPhoneUsage() {
476        long phoneOnTimeMs = mStats.getPhoneOnTime(mBatteryRealtime, mStatsType) / 1000;
477        double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
478                * phoneOnTimeMs / (60*60*1000);
479        addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
480    }
481
482    private void addScreenUsage() {
483        double power = 0;
484        long screenOnTimeMs = mStats.getScreenOnTime(mBatteryRealtime, mStatsType) / 1000;
485        power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
486        final double screenFullPower =
487                mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
488        for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
489            double screenBinPower = screenFullPower * (i + 0.5f)
490                    / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
491            long brightnessTime = mStats.getScreenBrightnessTime(i, mBatteryRealtime, mStatsType)
492                    / 1000;
493            double p = screenBinPower*brightnessTime;
494            if (DEBUG && p != 0) {
495                Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
496                        + " power=" + makemAh(p/(60*60*1000)));
497            }
498            power += p;
499        }
500        power /= (60*60*1000); // To hours
501        addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
502    }
503
504    private void addRadioUsage() {
505        double power = 0;
506        final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
507        long signalTimeMs = 0;
508        long noCoverageTimeMs = 0;
509        for (int i = 0; i < BINS; i++) {
510            long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mBatteryRealtime, mStatsType)
511                    / 1000;
512            double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i))
513                        / (60*60*1000);
514            if (DEBUG && p != 0) {
515                Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
516                        + makemAh(p));
517            }
518            power += p;
519            signalTimeMs += strengthTimeMs;
520            if (i == 0) {
521                noCoverageTimeMs = strengthTimeMs;
522            }
523        }
524        long scanningTimeMs = mStats.getPhoneSignalScanningTime(mBatteryRealtime, mStatsType)
525                / 1000;
526        double p = (scanningTimeMs * mPowerProfile.getAveragePower(
527                        PowerProfile.POWER_RADIO_SCANNING))
528                        / (60*60*1000);
529        if (DEBUG && p != 0) {
530            Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p));
531        }
532        power += p;
533        BatterySipper bs =
534                addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power);
535        if (signalTimeMs != 0) {
536            bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
537        }
538    }
539
540    private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
541        for (int i=0; i<from.size(); i++) {
542            BatterySipper wbs = from.get(i);
543            if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
544            bs.cpuTime += wbs.cpuTime;
545            bs.gpsTime += wbs.gpsTime;
546            bs.wifiRunningTime += wbs.wifiRunningTime;
547            bs.cpuFgTime += wbs.cpuFgTime;
548            bs.wakeLockTime += wbs.wakeLockTime;
549            bs.mobileRxPackets += wbs.mobileRxPackets;
550            bs.mobileTxPackets += wbs.mobileTxPackets;
551            bs.wifiRxPackets += wbs.wifiRxPackets;
552            bs.wifiTxPackets += wbs.wifiTxPackets;
553            bs.mobileRxBytes += wbs.mobileRxBytes;
554            bs.mobileTxBytes += wbs.mobileTxBytes;
555            bs.wifiRxBytes += wbs.wifiRxBytes;
556            bs.wifiTxBytes += wbs.wifiTxBytes;
557        }
558    }
559
560    private void addWiFiUsage() {
561        long onTimeMs = mStats.getWifiOnTime(mBatteryRealtime, mStatsType) / 1000;
562        long runningTimeMs = mStats.getGlobalWifiRunningTime(mBatteryRealtime, mStatsType) / 1000;
563        if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs
564                + " app runningTime=" + mAppWifiRunning);
565        runningTimeMs -= mAppWifiRunning;
566        if (runningTimeMs < 0) runningTimeMs = 0;
567        double wifiPower = (onTimeMs * 0 /* TODO */
568                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
569                + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON))
570                / (60*60*1000);
571        if (DEBUG && wifiPower != 0) {
572            Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower));
573        }
574        BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs,
575                wifiPower + mWifiPower);
576        aggregateSippers(bs, mWifiSippers, "WIFI");
577    }
578
579    private void addIdleUsage() {
580        long idleTimeMs = (mTypeBatteryRealtime
581                - mStats.getScreenOnTime(mBatteryRealtime, mStatsType)) / 1000;
582        double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
583                / (60*60*1000);
584        if (DEBUG && idlePower != 0) {
585            Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower));
586        }
587        addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower);
588    }
589
590    private void addBluetoothUsage() {
591        long btOnTimeMs = mStats.getBluetoothOnTime(mBatteryRealtime, mStatsType) / 1000;
592        double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
593                / (60*60*1000);
594        if (DEBUG && btPower != 0) {
595            Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower));
596        }
597        int btPingCount = mStats.getBluetoothPingCount();
598        double pingPower = (btPingCount
599                * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD))
600                / (60*60*1000);
601        if (DEBUG && pingPower != 0) {
602            Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower));
603        }
604        btPower += pingPower;
605        BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs,
606                btPower + mBluetoothPower);
607        aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
608    }
609
610    private void addUserUsage() {
611        for (int i=0; i<mUserSippers.size(); i++) {
612            final int userId = mUserSippers.keyAt(i);
613            final List<BatterySipper> sippers = mUserSippers.valueAt(i);
614            Double userPower = mUserPower.get(userId);
615            double power = (userPower != null) ? userPower : 0.0;
616            BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power);
617            bs.userId = userId;
618            aggregateSippers(bs, sippers, "User");
619        }
620    }
621
622    /**
623     * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
624     */
625    private double getMobilePowerPerPacket() {
626        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
627        final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
628                / 3600;
629
630        final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
631        final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
632        final long mobileData = mobileRx + mobileTx;
633
634        final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000;
635        final double mobilePps = radioDataUptimeMs != 0
636                ? mobileData / (double)radioDataUptimeMs
637                : (((double)MOBILE_BPS) / 8 / 2048);
638
639        return (MOBILE_POWER / mobilePps) / (60*60);
640    }
641
642    /**
643     * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
644     */
645    private double getWifiPowerPerPacket() {
646        final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
647        final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
648                / 3600;
649        return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
650    }
651
652    private void processMiscUsage() {
653        addUserUsage();
654        addPhoneUsage();
655        addScreenUsage();
656        addWiFiUsage();
657        addBluetoothUsage();
658        addIdleUsage(); // Not including cellular idle power
659        // Don't compute radio usage if it's a wifi-only device
660        ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
661                Context.CONNECTIVITY_SERVICE);
662        if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
663            addRadioUsage();
664        }
665    }
666
667    private BatterySipper addEntry(DrainType drainType, long time, double power) {
668        mTotalPower += power;
669        return addEntryNoTotal(drainType, time, power);
670    }
671
672    private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) {
673        if (power > mMaxPower) mMaxPower = power;
674        BatterySipper bs = new BatterySipper(drainType, null, new double[] {power});
675        bs.usageTime = time;
676        mUsageList.add(bs);
677        return bs;
678    }
679
680    public List<BatterySipper> getUsageList() {
681        return mUsageList;
682    }
683
684    public long getStatsPeriod() { return mStatsPeriod; }
685
686    public int getStatsType() { return mStatsType; };
687
688    public double getMaxPower() { return mMaxPower; }
689
690    public double getTotalPower() { return mTotalPower; }
691
692    public double getMinDrainedPower() {
693        return mMinDrainedPower;
694    }
695
696    public double getMaxDrainedPower() {
697        return mMaxDrainedPower;
698    }
699
700    private void load() {
701        if (mBatteryInfo == null) {
702            return;
703        }
704        try {
705            byte[] data = mBatteryInfo.getStatistics();
706            Parcel parcel = Parcel.obtain();
707            parcel.unmarshall(data, 0, data.length);
708            parcel.setDataPosition(0);
709            BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
710                    .createFromParcel(parcel);
711            stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
712            mStats = stats;
713        } catch (RemoteException e) {
714            Log.e(TAG, "RemoteException:", e);
715        }
716    }
717}
718