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