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