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