BatteryStatsHelper.java revision a7c837f043c1ca0bdecd42645ba7da8c5717566d
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            //mUsageList.add(new AppUsage(u.getUid(), new double[] {power}));
244            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
245            long cpuTime = 0;
246            long cpuFgTime = 0;
247            long wakelockTime = 0;
248            long gpsTime = 0;
249            if (processStats.size() > 0) {
250                // Process CPU time
251                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
252                        : processStats.entrySet()) {
253                    Uid.Proc ps = ent.getValue();
254                    final long userTime = ps.getUserTime(which);
255                    final long systemTime = ps.getSystemTime(which);
256                    final long foregroundTime = ps.getForegroundTime(which);
257                    cpuFgTime += foregroundTime * 10; // convert to millis
258                    final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
259                    int totalTimeAtSpeeds = 0;
260                    // Get the total first
261                    for (int step = 0; step < speedSteps; step++) {
262                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
263                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
264                    }
265                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
266                    // Then compute the ratio of time spent at each speed
267                    double processPower = 0;
268                    for (int step = 0; step < speedSteps; step++) {
269                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
270                        if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
271                                + step + " ratio=" + makemAh(ratio) + " power="
272                                + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));
273                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
274                    }
275                    cpuTime += tmpCpuTime;
276                    if (DEBUG && processPower != 0) {
277                        Log.d(TAG, String.format("process %s, cpu power=%s",
278                                ent.getKey(), makemAh(processPower / (60*60*1000))));
279                    }
280                    power += processPower;
281                    if (packageWithHighestDrain == null
282                            || packageWithHighestDrain.startsWith("*")) {
283                        highestDrain = processPower;
284                        packageWithHighestDrain = ent.getKey();
285                    } else if (highestDrain < processPower
286                            && !ent.getKey().startsWith("*")) {
287                        highestDrain = processPower;
288                        packageWithHighestDrain = ent.getKey();
289                    }
290                }
291            }
292            if (cpuFgTime > cpuTime) {
293                if (DEBUG && cpuFgTime > cpuTime + 10000) {
294                    Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
295                }
296                cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
297            }
298            power /= (60*60*1000);
299
300            // Process wake lock usage
301            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
302            for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
303                    : wakelockStats.entrySet()) {
304                Uid.Wakelock wakelock = wakelockEntry.getValue();
305                // Only care about partial wake locks since full wake locks
306                // are canceled when the user turns the screen off.
307                BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
308                if (timer != null) {
309                    wakelockTime += timer.getTotalTimeLocked(mBatteryRealtime, which);
310                }
311            }
312            wakelockTime /= 1000; // convert to millis
313            appWakelockTime += wakelockTime;
314
315            // Add cost of holding a wake lock
316            p = (wakelockTime
317                    * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
318            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
319                    + wakelockTime + " power=" + makemAh(p));
320            power += p;
321
322            // Add cost of mobile traffic
323            final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
324            final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
325            final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
326            final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
327            p = (mobileRx + mobileTx) * mobilePowerPerPacket;
328            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
329                    + (mobileRx+mobileTx) + " power=" + makemAh(p));
330            power += p;
331
332            // Add cost of wifi traffic
333            final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
334            final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
335            final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
336            final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
337            p = (wifiRx + wifiTx) * wifiPowerPerPacket;
338            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
339                    + (mobileRx+mobileTx) + " power=" + makemAh(p));
340            power += p;
341
342            // Add cost of keeping WIFI running.
343            long wifiRunningTimeMs = u.getWifiRunningTime(mBatteryRealtime, which) / 1000;
344            mAppWifiRunning += wifiRunningTimeMs;
345            p = (wifiRunningTimeMs
346                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
347            if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
348                    + wifiRunningTimeMs + " power=" + makemAh(p));
349            power += p;
350
351            // Add cost of WIFI scans
352            long wifiScanTimeMs = u.getWifiScanTime(mBatteryRealtime, which) / 1000;
353            p = (wifiScanTimeMs
354                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
355            if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
356                    + " power=" + makemAh(p));
357            power += p;
358            for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
359                long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mBatteryRealtime, which) / 1000;
360                p = ((batchScanTimeMs
361                        * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
362                    ) / (60*60*1000);
363                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
364                        + " time=" + batchScanTimeMs + " power=" + makemAh(p));
365                power += p;
366            }
367
368            // Process Sensor usage
369            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
370            for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
371                    : sensorStats.entrySet()) {
372                Uid.Sensor sensor = sensorEntry.getValue();
373                int sensorHandle = sensor.getHandle();
374                BatteryStats.Timer timer = sensor.getSensorTime();
375                long sensorTime = timer.getTotalTimeLocked(mBatteryRealtime, which) / 1000;
376                double multiplier = 0;
377                switch (sensorHandle) {
378                    case Uid.Sensor.GPS:
379                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
380                        gpsTime = sensorTime;
381                        break;
382                    default:
383                        List<Sensor> sensorList = sensorManager.getSensorList(
384                                android.hardware.Sensor.TYPE_ALL);
385                        for (android.hardware.Sensor s : sensorList) {
386                            if (s.getHandle() == sensorHandle) {
387                                multiplier = s.getPower();
388                                break;
389                            }
390                        }
391                }
392                p = (multiplier * sensorTime) / (60*60*1000);
393                if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
394                        + " time=" + sensorTime + " power=" + makemAh(p));
395                power += p;
396            }
397
398            if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
399                    u.getUid(), makemAh(power)));
400
401            // Add the app to the list if it is consuming power
402            boolean isOtherUser = false;
403            final int userId = UserHandle.getUserId(u.getUid());
404            if (power != 0 || u.getUid() == 0) {
405                BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
406                        new double[] {power});
407                app.cpuTime = cpuTime;
408                app.gpsTime = gpsTime;
409                app.wifiRunningTime = wifiRunningTimeMs;
410                app.cpuFgTime = cpuFgTime;
411                app.wakeLockTime = wakelockTime;
412                app.mobileRxPackets = mobileRx;
413                app.mobileTxPackets = mobileTx;
414                app.wifiRxPackets = wifiRx;
415                app.wifiTxPackets = wifiTx;
416                app.mobileRxBytes = mobileRxB;
417                app.mobileTxBytes = mobileTxB;
418                app.wifiRxBytes = wifiRxB;
419                app.wifiTxBytes = wifiTxB;
420                app.packageWithHighestDrain = packageWithHighestDrain;
421                if (u.getUid() == Process.WIFI_UID) {
422                    mWifiSippers.add(app);
423                } else if (u.getUid() == Process.BLUETOOTH_UID) {
424                    mBluetoothSippers.add(app);
425                } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser
426                        && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
427                    isOtherUser = true;
428                    List<BatterySipper> list = mUserSippers.get(userId);
429                    if (list == null) {
430                        list = new ArrayList<BatterySipper>();
431                        mUserSippers.put(userId, list);
432                    }
433                    list.add(app);
434                } else {
435                    mUsageList.add(app);
436                }
437                if (u.getUid() == 0) {
438                    osApp = app;
439                }
440            }
441            if (power != 0) {
442                if (u.getUid() == Process.WIFI_UID) {
443                    mWifiPower += power;
444                } else if (u.getUid() == Process.BLUETOOTH_UID) {
445                    mBluetoothPower += power;
446                } else if (isOtherUser) {
447                    Double userPower = mUserPower.get(userId);
448                    if (userPower == null) {
449                        userPower = power;
450                    } else {
451                        userPower += power;
452                    }
453                    mUserPower.put(userId, userPower);
454                } else {
455                    if (power > mMaxPower) mMaxPower = power;
456                    mTotalPower += power;
457                }
458            }
459        }
460
461        // The device has probably been awake for longer than the screen on
462        // time and application wake lock time would account for.  Assign
463        // this remainder to the OS, if possible.
464        if (osApp != null) {
465            long wakeTimeMillis = mBatteryUptime / 1000;
466            wakeTimeMillis -= appWakelockTime
467                    + (mStats.getScreenOnTime(mBatteryRealtime, which) / 1000);
468            if (wakeTimeMillis > 0) {
469                double power = (wakeTimeMillis
470                        * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
471                        /  (60*60*1000);
472                if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
473                        + makemAh(power));
474                osApp.wakeLockTime += wakeTimeMillis;
475                osApp.value += power;
476                osApp.values[0] += power;
477                if (osApp.value > mMaxPower) mMaxPower = osApp.value;
478                mTotalPower += power;
479            }
480        }
481    }
482
483    private void addPhoneUsage() {
484        long phoneOnTimeMs = mStats.getPhoneOnTime(mBatteryRealtime, mStatsType) / 1000;
485        double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
486                * phoneOnTimeMs / (60*60*1000);
487        addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
488    }
489
490    private void addScreenUsage() {
491        double power = 0;
492        long screenOnTimeMs = mStats.getScreenOnTime(mBatteryRealtime, mStatsType) / 1000;
493        power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
494        final double screenFullPower =
495                mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
496        for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
497            double screenBinPower = screenFullPower * (i + 0.5f)
498                    / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
499            long brightnessTime = mStats.getScreenBrightnessTime(i, mBatteryRealtime, mStatsType)
500                    / 1000;
501            double p = screenBinPower*brightnessTime;
502            if (DEBUG && p != 0) {
503                Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
504                        + " power=" + makemAh(p/(60*60*1000)));
505            }
506            power += p;
507        }
508        power /= (60*60*1000); // To hours
509        addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
510    }
511
512    private void addRadioUsage() {
513        double power = 0;
514        final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
515        long signalTimeMs = 0;
516        long noCoverageTimeMs = 0;
517        for (int i = 0; i < BINS; i++) {
518            long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mBatteryRealtime, mStatsType)
519                    / 1000;
520            double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i))
521                        / (60*60*1000);
522            if (DEBUG && p != 0) {
523                Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
524                        + makemAh(p));
525            }
526            power += p;
527            signalTimeMs += strengthTimeMs;
528            if (i == 0) {
529                noCoverageTimeMs = strengthTimeMs;
530            }
531        }
532        long scanningTimeMs = mStats.getPhoneSignalScanningTime(mBatteryRealtime, mStatsType)
533                / 1000;
534        double p = (scanningTimeMs * mPowerProfile.getAveragePower(
535                        PowerProfile.POWER_RADIO_SCANNING))
536                        / (60*60*1000);
537        if (DEBUG && p != 0) {
538            Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p));
539        }
540        power += p;
541        BatterySipper bs =
542                addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power);
543        if (signalTimeMs != 0) {
544            bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
545        }
546    }
547
548    private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
549        for (int i=0; i<from.size(); i++) {
550            BatterySipper wbs = from.get(i);
551            if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
552            bs.cpuTime += wbs.cpuTime;
553            bs.gpsTime += wbs.gpsTime;
554            bs.wifiRunningTime += wbs.wifiRunningTime;
555            bs.cpuFgTime += wbs.cpuFgTime;
556            bs.wakeLockTime += wbs.wakeLockTime;
557            bs.mobileRxPackets += wbs.mobileRxPackets;
558            bs.mobileTxPackets += wbs.mobileTxPackets;
559            bs.wifiRxPackets += wbs.wifiRxPackets;
560            bs.wifiTxPackets += wbs.wifiTxPackets;
561            bs.mobileRxBytes += wbs.mobileRxBytes;
562            bs.mobileTxBytes += wbs.mobileTxBytes;
563            bs.wifiRxBytes += wbs.wifiRxBytes;
564            bs.wifiTxBytes += wbs.wifiTxBytes;
565        }
566    }
567
568    private void addWiFiUsage() {
569        long onTimeMs = mStats.getWifiOnTime(mBatteryRealtime, mStatsType) / 1000;
570        long runningTimeMs = mStats.getGlobalWifiRunningTime(mBatteryRealtime, mStatsType) / 1000;
571        if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs
572                + " app runningTime=" + mAppWifiRunning);
573        runningTimeMs -= mAppWifiRunning;
574        if (runningTimeMs < 0) runningTimeMs = 0;
575        double wifiPower = (onTimeMs * 0 /* TODO */
576                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
577                + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON))
578                / (60*60*1000);
579        if (DEBUG && wifiPower != 0) {
580            Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower));
581        }
582        BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs,
583                wifiPower + mWifiPower);
584        aggregateSippers(bs, mWifiSippers, "WIFI");
585    }
586
587    private void addIdleUsage() {
588        long idleTimeMs = (mTypeBatteryRealtime
589                - mStats.getScreenOnTime(mBatteryRealtime, mStatsType)) / 1000;
590        double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
591                / (60*60*1000);
592        if (DEBUG && idlePower != 0) {
593            Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower));
594        }
595        addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower);
596    }
597
598    private void addBluetoothUsage() {
599        long btOnTimeMs = mStats.getBluetoothOnTime(mBatteryRealtime, mStatsType) / 1000;
600        double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
601                / (60*60*1000);
602        if (DEBUG && btPower != 0) {
603            Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower));
604        }
605        int btPingCount = mStats.getBluetoothPingCount();
606        double pingPower = (btPingCount
607                * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD))
608                / (60*60*1000);
609        if (DEBUG && pingPower != 0) {
610            Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower));
611        }
612        btPower += pingPower;
613        BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs,
614                btPower + mBluetoothPower);
615        aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
616    }
617
618    private void addUserUsage() {
619        for (int i=0; i<mUserSippers.size(); i++) {
620            final int userId = mUserSippers.keyAt(i);
621            final List<BatterySipper> sippers = mUserSippers.valueAt(i);
622            Double userPower = mUserPower.get(userId);
623            double power = (userPower != null) ? userPower : 0.0;
624            BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power);
625            bs.userId = userId;
626            aggregateSippers(bs, sippers, "User");
627        }
628    }
629
630    /**
631     * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
632     */
633    private double getMobilePowerPerPacket() {
634        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
635        final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
636                / 3600;
637
638        final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
639        final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
640        final long mobileData = mobileRx + mobileTx;
641
642        final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000;
643        final double mobilePps = radioDataUptimeMs != 0
644                ? mobileData / (double)radioDataUptimeMs
645                : (((double)MOBILE_BPS) / 8 / 2048);
646
647        return (MOBILE_POWER / mobilePps) / (60*60);
648    }
649
650    /**
651     * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
652     */
653    private double getWifiPowerPerPacket() {
654        final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
655        final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
656                / 3600;
657        return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
658    }
659
660    private void processMiscUsage() {
661        addUserUsage();
662        addPhoneUsage();
663        addScreenUsage();
664        addWiFiUsage();
665        addBluetoothUsage();
666        addIdleUsage(); // Not including cellular idle power
667        // Don't compute radio usage if it's a wifi-only device
668        ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
669                Context.CONNECTIVITY_SERVICE);
670        if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
671            addRadioUsage();
672        }
673    }
674
675    private BatterySipper addEntry(DrainType drainType, long time, double power) {
676        mTotalPower += power;
677        return addEntryNoTotal(drainType, time, power);
678    }
679
680    private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) {
681        if (power > mMaxPower) mMaxPower = power;
682        mTotalPower += power;
683        BatterySipper bs = new BatterySipper(drainType, null, new double[] {power});
684        bs.usageTime = time;
685        mUsageList.add(bs);
686        return bs;
687    }
688
689    public List<BatterySipper> getUsageList() {
690        return mUsageList;
691    }
692
693    public long getStatsPeriod() { return mStatsPeriod; }
694
695    public int getStatsType() { return mStatsType; };
696
697    public double getMaxPower() { return mMaxPower; }
698
699    public double getTotalPower() { return mTotalPower; }
700
701    public double getMinDrainedPower() {
702        return mMinDrainedPower;
703    }
704
705    public double getMaxDrainedPower() {
706        return mMaxDrainedPower;
707    }
708
709    private void load() {
710        if (mBatteryInfo == null) {
711            return;
712        }
713        try {
714            byte[] data = mBatteryInfo.getStatistics();
715            Parcel parcel = Parcel.obtain();
716            parcel.unmarshall(data, 0, data.length);
717            parcel.setDataPosition(0);
718            BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
719                    .createFromParcel(parcel);
720            stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
721            mStats = stats;
722        } catch (RemoteException e) {
723            Log.e(TAG, "RemoteException:", e);
724        }
725    }
726}
727