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.settings.fuelgauge;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.hardware.SensorManager;
24import android.os.BatteryStats;
25import android.os.BatteryStats.Uid;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.Message;
29import android.os.Parcel;
30import android.os.Process;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.os.SystemClock;
34import android.preference.Preference;
35import android.preference.PreferenceActivity;
36import android.preference.PreferenceFragment;
37import android.preference.PreferenceGroup;
38import android.preference.PreferenceScreen;
39import android.telephony.SignalStrength;
40import android.util.Log;
41import android.util.SparseArray;
42import android.view.Menu;
43import android.view.MenuInflater;
44import android.view.MenuItem;
45
46import com.android.internal.app.IBatteryStats;
47import com.android.internal.os.BatteryStatsImpl;
48import com.android.internal.os.PowerProfile;
49import com.android.settings.R;
50import com.android.settings.fuelgauge.PowerUsageDetail.DrainType;
51
52import java.io.PrintWriter;
53import java.io.StringWriter;
54import java.io.Writer;
55import java.util.ArrayList;
56import java.util.Collections;
57import java.util.List;
58import java.util.Map;
59
60/**
61 * Displays a list of apps and subsystems that consume power, ordered by how much power was
62 * consumed since the last time it was unplugged.
63 */
64public class PowerUsageSummary extends PreferenceFragment implements Runnable {
65
66    private static final boolean DEBUG = false;
67
68    private static final String TAG = "PowerUsageSummary";
69
70    private static final String KEY_APP_LIST = "app_list";
71    private static final String KEY_BATTERY_STATUS = "battery_status";
72
73    private static final int MENU_STATS_TYPE = Menu.FIRST;
74    private static final int MENU_STATS_REFRESH = Menu.FIRST + 1;
75
76    private static BatteryStatsImpl sStatsXfer;
77
78    IBatteryStats mBatteryInfo;
79    BatteryStatsImpl mStats;
80    private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
81    private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
82    private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
83
84    private PreferenceGroup mAppListGroup;
85    private Preference mBatteryStatusPref;
86
87    private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
88
89    private static final int MIN_POWER_THRESHOLD = 5;
90    private static final int MAX_ITEMS_TO_LIST = 10;
91
92    private long mStatsPeriod = 0;
93    private double mMaxPower = 1;
94    private double mTotalPower;
95    private double mWifiPower;
96    private double mBluetoothPower;
97    private PowerProfile mPowerProfile;
98
99    // How much the apps together have left WIFI running.
100    private long mAppWifiRunning;
101
102    /** Queue for fetching name and icon for an application */
103    private ArrayList<BatterySipper> mRequestQueue = new ArrayList<BatterySipper>();
104    private Thread mRequestThread;
105    private boolean mAbort;
106
107    private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
108
109        @Override
110        public void onReceive(Context context, Intent intent) {
111            String action = intent.getAction();
112            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
113                String batteryLevel = com.android.settings.Utils.getBatteryPercentage(intent);
114                String batteryStatus = com.android.settings.Utils.getBatteryStatus(getResources(),
115                        intent);
116                String batterySummary = context.getResources().getString(
117                        R.string.power_usage_level_and_status, batteryLevel, batteryStatus);
118                mBatteryStatusPref.setTitle(batterySummary);
119            }
120        }
121    };
122
123    @Override
124    public void onCreate(Bundle icicle) {
125        super.onCreate(icicle);
126
127        if (icicle != null) {
128            mStats = sStatsXfer;
129        }
130
131        addPreferencesFromResource(R.xml.power_usage_summary);
132        mBatteryInfo = IBatteryStats.Stub.asInterface(
133                ServiceManager.getService("batteryinfo"));
134        mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
135        mBatteryStatusPref = mAppListGroup.findPreference(KEY_BATTERY_STATUS);
136        mPowerProfile = new PowerProfile(getActivity());
137        setHasOptionsMenu(true);
138    }
139
140    @Override
141    public void onResume() {
142        super.onResume();
143        mAbort = false;
144        getActivity().registerReceiver(mBatteryInfoReceiver,
145                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
146        refreshStats();
147    }
148
149    @Override
150    public void onPause() {
151        synchronized (mRequestQueue) {
152            mAbort = true;
153        }
154        mHandler.removeMessages(MSG_UPDATE_NAME_ICON);
155        getActivity().unregisterReceiver(mBatteryInfoReceiver);
156        super.onPause();
157    }
158
159    @Override
160    public void onDestroy() {
161        super.onDestroy();
162        if (getActivity().isChangingConfigurations()) {
163            sStatsXfer = mStats;
164        }
165    }
166
167    @Override
168    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
169        if (preference instanceof BatteryHistoryPreference) {
170            Parcel hist = Parcel.obtain();
171            mStats.writeToParcelWithoutUids(hist, 0);
172            byte[] histData = hist.marshall();
173            Bundle args = new Bundle();
174            args.putByteArray(BatteryHistoryDetail.EXTRA_STATS, histData);
175            PreferenceActivity pa = (PreferenceActivity)getActivity();
176            pa.startPreferencePanel(BatteryHistoryDetail.class.getName(), args,
177                    R.string.history_details_title, null, null, 0);
178            return super.onPreferenceTreeClick(preferenceScreen, preference);
179        }
180        if (!(preference instanceof PowerGaugePreference)) {
181            return false;
182        }
183        PowerGaugePreference pgp = (PowerGaugePreference) preference;
184        BatterySipper sipper = pgp.getInfo();
185        Bundle args = new Bundle();
186        args.putString(PowerUsageDetail.EXTRA_TITLE, sipper.name);
187        args.putInt(PowerUsageDetail.EXTRA_PERCENT, (int)
188                Math.ceil(sipper.getSortValue() * 100 / mTotalPower));
189        args.putInt(PowerUsageDetail.EXTRA_GAUGE, (int)
190                Math.ceil(sipper.getSortValue() * 100 / mMaxPower));
191        args.putLong(PowerUsageDetail.EXTRA_USAGE_DURATION, mStatsPeriod);
192        args.putString(PowerUsageDetail.EXTRA_ICON_PACKAGE, sipper.defaultPackageName);
193        args.putInt(PowerUsageDetail.EXTRA_ICON_ID, sipper.iconId);
194        args.putDouble(PowerUsageDetail.EXTRA_NO_COVERAGE, sipper.noCoveragePercent);
195        if (sipper.uidObj != null) {
196            args.putInt(PowerUsageDetail.EXTRA_UID, sipper.uidObj.getUid());
197        }
198        args.putSerializable(PowerUsageDetail.EXTRA_DRAIN_TYPE, sipper.drainType);
199
200        int[] types;
201        double[] values;
202        switch (sipper.drainType) {
203            case APP:
204            {
205                Uid uid = sipper.uidObj;
206                types = new int[] {
207                    R.string.usage_type_cpu,
208                    R.string.usage_type_cpu_foreground,
209                    R.string.usage_type_wake_lock,
210                    R.string.usage_type_gps,
211                    R.string.usage_type_wifi_running,
212                    R.string.usage_type_data_send,
213                    R.string.usage_type_data_recv,
214                    R.string.usage_type_audio,
215                    R.string.usage_type_video,
216                };
217                values = new double[] {
218                    sipper.cpuTime,
219                    sipper.cpuFgTime,
220                    sipper.wakeLockTime,
221                    sipper.gpsTime,
222                    sipper.wifiRunningTime,
223                    sipper.tcpBytesSent,
224                    sipper.tcpBytesReceived,
225                    0,
226                    0
227                };
228
229                Writer result = new StringWriter();
230                PrintWriter printWriter = new PrintWriter(result);
231                mStats.dumpLocked(printWriter, "", mStatsType, uid.getUid());
232                args.putString(PowerUsageDetail.EXTRA_REPORT_DETAILS, result.toString());
233
234                result = new StringWriter();
235                printWriter = new PrintWriter(result);
236                mStats.dumpCheckinLocked(printWriter, mStatsType, uid.getUid());
237                args.putString(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS, result.toString());
238            }
239            break;
240            case CELL:
241            {
242                types = new int[] {
243                    R.string.usage_type_on_time,
244                    R.string.usage_type_no_coverage
245                };
246                values = new double[] {
247                    sipper.usageTime,
248                    sipper.noCoveragePercent
249                };
250            }
251            break;
252            case WIFI:
253            {
254                types = new int[] {
255                    R.string.usage_type_wifi_running,
256                    R.string.usage_type_cpu,
257                    R.string.usage_type_cpu_foreground,
258                    R.string.usage_type_wake_lock,
259                    R.string.usage_type_data_send,
260                    R.string.usage_type_data_recv,
261                };
262                values = new double[] {
263                    sipper.usageTime,
264                    sipper.cpuTime,
265                    sipper.cpuFgTime,
266                    sipper.wakeLockTime,
267                    sipper.tcpBytesSent,
268                    sipper.tcpBytesReceived,
269                };
270            } break;
271            case BLUETOOTH:
272            {
273                types = new int[] {
274                    R.string.usage_type_on_time,
275                    R.string.usage_type_cpu,
276                    R.string.usage_type_cpu_foreground,
277                    R.string.usage_type_wake_lock,
278                    R.string.usage_type_data_send,
279                    R.string.usage_type_data_recv,
280                };
281                values = new double[] {
282                    sipper.usageTime,
283                    sipper.cpuTime,
284                    sipper.cpuFgTime,
285                    sipper.wakeLockTime,
286                    sipper.tcpBytesSent,
287                    sipper.tcpBytesReceived,
288                };
289            } break;
290            default:
291            {
292                types = new int[] {
293                    R.string.usage_type_on_time
294                };
295                values = new double[] {
296                    sipper.usageTime
297                };
298            }
299        }
300        args.putIntArray(PowerUsageDetail.EXTRA_DETAIL_TYPES, types);
301        args.putDoubleArray(PowerUsageDetail.EXTRA_DETAIL_VALUES, values);
302        PreferenceActivity pa = (PreferenceActivity)getActivity();
303        pa.startPreferencePanel(PowerUsageDetail.class.getName(), args,
304                R.string.details_title, null, null, 0);
305
306        return super.onPreferenceTreeClick(preferenceScreen, preference);
307    }
308
309    @Override
310    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
311        if (DEBUG) {
312            menu.add(0, MENU_STATS_TYPE, 0, R.string.menu_stats_total)
313                    .setIcon(com.android.internal.R.drawable.ic_menu_info_details)
314                    .setAlphabeticShortcut('t');
315        }
316        MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh)
317                .setIcon(R.drawable.ic_menu_refresh_holo_dark)
318                .setAlphabeticShortcut('r');
319        refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
320                MenuItem.SHOW_AS_ACTION_WITH_TEXT);
321    }
322
323    @Override
324    public boolean onOptionsItemSelected(MenuItem item) {
325        switch (item.getItemId()) {
326            case MENU_STATS_TYPE:
327                if (mStatsType == BatteryStats.STATS_SINCE_CHARGED) {
328                    mStatsType = BatteryStats.STATS_SINCE_UNPLUGGED;
329                } else {
330                    mStatsType = BatteryStats.STATS_SINCE_CHARGED;
331                }
332                refreshStats();
333                return true;
334            case MENU_STATS_REFRESH:
335                mStats = null;
336                refreshStats();
337                return true;
338            default:
339                return false;
340        }
341    }
342
343    private void addNotAvailableMessage() {
344        Preference notAvailable = new Preference(getActivity());
345        notAvailable.setTitle(R.string.power_usage_not_available);
346        mAppListGroup.addPreference(notAvailable);
347    }
348
349    private void refreshStats() {
350        if (mStats == null) {
351            load();
352        }
353        mMaxPower = 0;
354        mTotalPower = 0;
355        mWifiPower = 0;
356        mBluetoothPower = 0;
357        mAppWifiRunning = 0;
358
359        mAppListGroup.removeAll();
360        mUsageList.clear();
361        mWifiSippers.clear();
362        mBluetoothSippers.clear();
363        mAppListGroup.setOrderingAsAdded(false);
364
365        mBatteryStatusPref.setOrder(-2);
366        mAppListGroup.addPreference(mBatteryStatusPref);
367        BatteryHistoryPreference hist = new BatteryHistoryPreference(getActivity(), mStats);
368        hist.setOrder(-1);
369        mAppListGroup.addPreference(hist);
370
371        if (mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL) < 10) {
372            addNotAvailableMessage();
373            return;
374        }
375        processAppUsage();
376        processMiscUsage();
377
378        Collections.sort(mUsageList);
379        for (BatterySipper sipper : mUsageList) {
380            if (sipper.getSortValue() < MIN_POWER_THRESHOLD) continue;
381            final double percentOfTotal =  ((sipper.getSortValue() / mTotalPower) * 100);
382            if (percentOfTotal < 1) continue;
383            PowerGaugePreference pref = new PowerGaugePreference(getActivity(), sipper.getIcon(), sipper);
384            final double percentOfMax = (sipper.getSortValue() * 100) / mMaxPower;
385            sipper.percent = percentOfTotal;
386            pref.setTitle(sipper.name);
387            pref.setOrder(Integer.MAX_VALUE - (int) sipper.getSortValue()); // Invert the order
388            pref.setPercent(percentOfMax, percentOfTotal);
389            if (sipper.uidObj != null) {
390                pref.setKey(Integer.toString(sipper.uidObj.getUid()));
391            }
392            mAppListGroup.addPreference(pref);
393            if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST+1)) break;
394        }
395        synchronized (mRequestQueue) {
396            if (!mRequestQueue.isEmpty()) {
397                if (mRequestThread == null) {
398                    mRequestThread = new Thread(this, "BatteryUsage Icon Loader");
399                    mRequestThread.setPriority(Thread.MIN_PRIORITY);
400                    mRequestThread.start();
401                }
402                mRequestQueue.notify();
403            }
404        }
405    }
406
407    private void processAppUsage() {
408        SensorManager sensorManager = (SensorManager)getActivity().getSystemService(
409                Context.SENSOR_SERVICE);
410        final int which = mStatsType;
411        final int speedSteps = mPowerProfile.getNumSpeedSteps();
412        final double[] powerCpuNormal = new double[speedSteps];
413        final long[] cpuSpeedStepTimes = new long[speedSteps];
414        for (int p = 0; p < speedSteps; p++) {
415            powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
416        }
417        final double averageCostPerByte = getAverageDataCost();
418        long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which);
419        long appWakelockTime = 0;
420        BatterySipper osApp = null;
421        mStatsPeriod = uSecTime;
422        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
423        final int NU = uidStats.size();
424        for (int iu = 0; iu < NU; iu++) {
425            Uid u = uidStats.valueAt(iu);
426            double power = 0;
427            double highestDrain = 0;
428            String packageWithHighestDrain = null;
429            //mUsageList.add(new AppUsage(u.getUid(), new double[] {power}));
430            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
431            long cpuTime = 0;
432            long cpuFgTime = 0;
433            long wakelockTime = 0;
434            long gpsTime = 0;
435            if (processStats.size() > 0) {
436                // Process CPU time
437                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
438                        : processStats.entrySet()) {
439                    if (DEBUG) Log.i(TAG, "Process name = " + ent.getKey());
440                    Uid.Proc ps = ent.getValue();
441                    final long userTime = ps.getUserTime(which);
442                    final long systemTime = ps.getSystemTime(which);
443                    final long foregroundTime = ps.getForegroundTime(which);
444                    cpuFgTime += foregroundTime * 10; // convert to millis
445                    final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
446                    int totalTimeAtSpeeds = 0;
447                    // Get the total first
448                    for (int step = 0; step < speedSteps; step++) {
449                        cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
450                        totalTimeAtSpeeds += cpuSpeedStepTimes[step];
451                    }
452                    if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
453                    // Then compute the ratio of time spent at each speed
454                    double processPower = 0;
455                    for (int step = 0; step < speedSteps; step++) {
456                        double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
457                        processPower += ratio * tmpCpuTime * powerCpuNormal[step];
458                    }
459                    cpuTime += tmpCpuTime;
460                    power += processPower;
461                    if (packageWithHighestDrain == null
462                            || packageWithHighestDrain.startsWith("*")) {
463                        highestDrain = processPower;
464                        packageWithHighestDrain = ent.getKey();
465                    } else if (highestDrain < processPower
466                            && !ent.getKey().startsWith("*")) {
467                        highestDrain = processPower;
468                        packageWithHighestDrain = ent.getKey();
469                    }
470                }
471                if (DEBUG) Log.i(TAG, "Max drain of " + highestDrain
472                        + " by " + packageWithHighestDrain);
473            }
474            if (cpuFgTime > cpuTime) {
475                if (DEBUG && cpuFgTime > cpuTime + 10000) {
476                    Log.i(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
477                }
478                cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
479            }
480            power /= 1000;
481
482            // Process wake lock usage
483            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
484            for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
485                    : wakelockStats.entrySet()) {
486                Uid.Wakelock wakelock = wakelockEntry.getValue();
487                // Only care about partial wake locks since full wake locks
488                // are canceled when the user turns the screen off.
489                BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
490                if (timer != null) {
491                    wakelockTime += timer.getTotalTimeLocked(uSecTime, which);
492                }
493            }
494            wakelockTime /= 1000; // convert to millis
495            appWakelockTime += wakelockTime;
496
497            // Add cost of holding a wake lock
498            power += (wakelockTime
499                    * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000;
500
501            // Add cost of data traffic
502            long tcpBytesReceived = u.getTcpBytesReceived(mStatsType);
503            long tcpBytesSent = u.getTcpBytesSent(mStatsType);
504            power += (tcpBytesReceived+tcpBytesSent) * averageCostPerByte;
505
506            // Add cost of keeping WIFI running.
507            long wifiRunningTimeMs = u.getWifiRunningTime(uSecTime, which) / 1000;
508            mAppWifiRunning += wifiRunningTimeMs;
509            power += (wifiRunningTimeMs
510                    * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000;
511
512            // Process Sensor usage
513            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
514            for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
515                    : sensorStats.entrySet()) {
516                Uid.Sensor sensor = sensorEntry.getValue();
517                int sensorType = sensor.getHandle();
518                BatteryStats.Timer timer = sensor.getSensorTime();
519                long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000;
520                double multiplier = 0;
521                switch (sensorType) {
522                    case Uid.Sensor.GPS:
523                        multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
524                        gpsTime = sensorTime;
525                        break;
526                    default:
527                        android.hardware.Sensor sensorData =
528                                sensorManager.getDefaultSensor(sensorType);
529                        if (sensorData != null) {
530                            multiplier = sensorData.getPower();
531                            if (DEBUG) {
532                                Log.i(TAG, "Got sensor " + sensorData.getName() + " with power = "
533                                        + multiplier);
534                            }
535                        }
536                }
537                power += (multiplier * sensorTime) / 1000;
538            }
539
540            if (DEBUG) Log.i(TAG, "UID " + u.getUid() + ": power=" + power);
541
542            // Add the app to the list if it is consuming power
543            if (power != 0 || u.getUid() == 0) {
544                BatterySipper app = new BatterySipper(getActivity(), mRequestQueue, mHandler,
545                        packageWithHighestDrain, DrainType.APP, 0, u,
546                        new double[] {power});
547                app.cpuTime = cpuTime;
548                app.gpsTime = gpsTime;
549                app.wifiRunningTime = wifiRunningTimeMs;
550                app.cpuFgTime = cpuFgTime;
551                app.wakeLockTime = wakelockTime;
552                app.tcpBytesReceived = tcpBytesReceived;
553                app.tcpBytesSent = tcpBytesSent;
554                if (u.getUid() == Process.WIFI_UID) {
555                    mWifiSippers.add(app);
556                } else if (u.getUid() == Process.BLUETOOTH_GID) {
557                    mBluetoothSippers.add(app);
558                } else {
559                    mUsageList.add(app);
560                }
561                if (u.getUid() == 0) {
562                    osApp = app;
563                }
564            }
565            if (u.getUid() == Process.WIFI_UID) {
566                mWifiPower += power;
567            } else if (u.getUid() == Process.BLUETOOTH_GID) {
568                mBluetoothPower += power;
569            } else {
570                if (power > mMaxPower) mMaxPower = power;
571                mTotalPower += power;
572            }
573            if (DEBUG) Log.i(TAG, "Added power = " + power);
574        }
575
576        // The device has probably been awake for longer than the screen on
577        // time and application wake lock time would account for.  Assign
578        // this remainder to the OS, if possible.
579        if (osApp != null) {
580            long wakeTimeMillis = mStats.computeBatteryUptime(
581                    SystemClock.uptimeMillis() * 1000, which) / 1000;
582            wakeTimeMillis -= appWakelockTime - (mStats.getScreenOnTime(
583                    SystemClock.elapsedRealtime(), which) / 1000);
584            if (wakeTimeMillis > 0) {
585                double power = (wakeTimeMillis
586                        * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / 1000;
587                osApp.wakeLockTime += wakeTimeMillis;
588                osApp.value += power;
589                osApp.values[0] += power;
590                if (osApp.value > mMaxPower) mMaxPower = osApp.value;
591                mTotalPower += power;
592            }
593        }
594    }
595
596    private void addPhoneUsage(long uSecNow) {
597        long phoneOnTimeMs = mStats.getPhoneOnTime(uSecNow, mStatsType) / 1000;
598        double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
599                * phoneOnTimeMs / 1000;
600        addEntry(getActivity().getString(R.string.power_phone), DrainType.PHONE, phoneOnTimeMs,
601                R.drawable.ic_settings_voice_calls, phoneOnPower);
602    }
603
604    private void addScreenUsage(long uSecNow) {
605        double power = 0;
606        long screenOnTimeMs = mStats.getScreenOnTime(uSecNow, mStatsType) / 1000;
607        power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
608        final double screenFullPower =
609                mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
610        for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
611            double screenBinPower = screenFullPower * (i + 0.5f)
612                    / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
613            long brightnessTime = mStats.getScreenBrightnessTime(i, uSecNow, mStatsType) / 1000;
614            power += screenBinPower * brightnessTime;
615            if (DEBUG) {
616                Log.i(TAG, "Screen bin power = " + (int) screenBinPower + ", time = "
617                        + brightnessTime);
618            }
619        }
620        power /= 1000; // To seconds
621        addEntry(getActivity().getString(R.string.power_screen), DrainType.SCREEN, screenOnTimeMs,
622                R.drawable.ic_settings_display, power);
623    }
624
625    private void addRadioUsage(long uSecNow) {
626        double power = 0;
627        final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
628        long signalTimeMs = 0;
629        for (int i = 0; i < BINS; i++) {
630            long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000;
631            power += strengthTimeMs / 1000
632                    * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
633            signalTimeMs += strengthTimeMs;
634        }
635        long scanningTimeMs = mStats.getPhoneSignalScanningTime(uSecNow, mStatsType) / 1000;
636        power += scanningTimeMs / 1000 * mPowerProfile.getAveragePower(
637                PowerProfile.POWER_RADIO_SCANNING);
638        BatterySipper bs =
639                addEntry(getActivity().getString(R.string.power_cell), DrainType.CELL,
640                        signalTimeMs, R.drawable.ic_settings_cell_standby, power);
641        if (signalTimeMs != 0) {
642            bs.noCoveragePercent = mStats.getPhoneSignalStrengthTime(0, uSecNow, mStatsType)
643                    / 1000 * 100.0 / signalTimeMs;
644        }
645    }
646
647    private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
648        for (int i=0; i<from.size(); i++) {
649            BatterySipper wbs = from.get(i);
650            if (DEBUG) Log.i(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
651            bs.cpuTime += wbs.cpuTime;
652            bs.gpsTime += wbs.gpsTime;
653            bs.wifiRunningTime += wbs.wifiRunningTime;
654            bs.cpuFgTime += wbs.cpuFgTime;
655            bs.wakeLockTime += wbs.wakeLockTime;
656            bs.tcpBytesReceived += wbs.tcpBytesReceived;
657            bs.tcpBytesSent += wbs.tcpBytesSent;
658        }
659    }
660
661    private void addWiFiUsage(long uSecNow) {
662        long onTimeMs = mStats.getWifiOnTime(uSecNow, mStatsType) / 1000;
663        long runningTimeMs = mStats.getGlobalWifiRunningTime(uSecNow, mStatsType) / 1000;
664        if (DEBUG) Log.i(TAG, "WIFI runningTime=" + runningTimeMs
665                + " app runningTime=" + mAppWifiRunning);
666        runningTimeMs -= mAppWifiRunning;
667        if (runningTimeMs < 0) runningTimeMs = 0;
668        double wifiPower = (onTimeMs * 0 /* TODO */
669                * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
670            + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000;
671        if (DEBUG) Log.i(TAG, "WIFI power=" + wifiPower + " from procs=" + mWifiPower);
672        BatterySipper bs = addEntry(getActivity().getString(R.string.power_wifi), DrainType.WIFI,
673                runningTimeMs, R.drawable.ic_settings_wifi, wifiPower + mWifiPower);
674        aggregateSippers(bs, mWifiSippers, "WIFI");
675    }
676
677    private void addIdleUsage(long uSecNow) {
678        long idleTimeMs = (uSecNow - mStats.getScreenOnTime(uSecNow, mStatsType)) / 1000;
679        double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
680                / 1000;
681        addEntry(getActivity().getString(R.string.power_idle), DrainType.IDLE, idleTimeMs,
682                R.drawable.ic_settings_phone_idle, idlePower);
683    }
684
685    private void addBluetoothUsage(long uSecNow) {
686        long btOnTimeMs = mStats.getBluetoothOnTime(uSecNow, mStatsType) / 1000;
687        double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
688                / 1000;
689        int btPingCount = mStats.getBluetoothPingCount();
690        btPower += (btPingCount
691                * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD)) / 1000;
692        BatterySipper bs = addEntry(getActivity().getString(R.string.power_bluetooth),
693                DrainType.BLUETOOTH, btOnTimeMs, R.drawable.ic_settings_bluetooth,
694                btPower + mBluetoothPower);
695        aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
696    }
697
698    private double getAverageDataCost() {
699        final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
700        final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
701        final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
702                / 3600;
703        final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
704                / 3600;
705        final long mobileData = mStats.getMobileTcpBytesReceived(mStatsType) +
706                mStats.getMobileTcpBytesSent(mStatsType);
707        final long wifiData = mStats.getTotalTcpBytesReceived(mStatsType) +
708                mStats.getTotalTcpBytesSent(mStatsType) - mobileData;
709        final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000;
710        final long mobileBps = radioDataUptimeMs != 0
711                ? mobileData * 8 * 1000 / radioDataUptimeMs
712                : MOBILE_BPS;
713
714        double mobileCostPerByte = MOBILE_POWER / (mobileBps / 8);
715        double wifiCostPerByte = WIFI_POWER / (WIFI_BPS / 8);
716        if (wifiData + mobileData != 0) {
717            return (mobileCostPerByte * mobileData + wifiCostPerByte * wifiData)
718                    / (mobileData + wifiData);
719        } else {
720            return 0;
721        }
722    }
723
724    private void processMiscUsage() {
725        final int which = mStatsType;
726        long uSecTime = SystemClock.elapsedRealtime() * 1000;
727        final long uSecNow = mStats.computeBatteryRealtime(uSecTime, which);
728        final long timeSinceUnplugged = uSecNow;
729        if (DEBUG) {
730            Log.i(TAG, "Uptime since last unplugged = " + (timeSinceUnplugged / 1000));
731        }
732
733        addPhoneUsage(uSecNow);
734        addScreenUsage(uSecNow);
735        addWiFiUsage(uSecNow);
736        addBluetoothUsage(uSecNow);
737        addIdleUsage(uSecNow); // Not including cellular idle power
738        // Don't compute radio usage if it's a wifi-only device
739        if (!com.android.settings.Utils.isWifiOnly(getActivity())) {
740            addRadioUsage(uSecNow);
741        }
742    }
743
744    private BatterySipper addEntry(String label, DrainType drainType, long time, int iconId,
745            double power) {
746        if (power > mMaxPower) mMaxPower = power;
747        mTotalPower += power;
748        BatterySipper bs = new BatterySipper(getActivity(), mRequestQueue, mHandler,
749                label, drainType, iconId, null, new double[] {power});
750        bs.usageTime = time;
751        bs.iconId = iconId;
752        mUsageList.add(bs);
753        return bs;
754    }
755
756    private void load() {
757        try {
758            byte[] data = mBatteryInfo.getStatistics();
759            Parcel parcel = Parcel.obtain();
760            parcel.unmarshall(data, 0, data.length);
761            parcel.setDataPosition(0);
762            mStats = com.android.internal.os.BatteryStatsImpl.CREATOR
763                    .createFromParcel(parcel);
764            mStats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
765        } catch (RemoteException e) {
766            Log.e(TAG, "RemoteException:", e);
767        }
768    }
769
770    public void run() {
771        while (true) {
772            BatterySipper bs;
773            synchronized (mRequestQueue) {
774                if (mRequestQueue.isEmpty() || mAbort) {
775                    mRequestThread = null;
776                    return;
777                }
778                bs = mRequestQueue.remove(0);
779            }
780            bs.getNameIcon();
781        }
782    }
783
784    static final int MSG_UPDATE_NAME_ICON = 1;
785
786    Handler mHandler = new Handler() {
787
788        @Override
789        public void handleMessage(Message msg) {
790            switch (msg.what) {
791                case MSG_UPDATE_NAME_ICON:
792                    BatterySipper bs = (BatterySipper) msg.obj;
793                    PowerGaugePreference pgp =
794                            (PowerGaugePreference) findPreference(
795                                    Integer.toString(bs.uidObj.getUid()));
796                    if (pgp != null) {
797                        pgp.setIcon(bs.icon);
798                        pgp.setTitle(bs.name);
799                    }
800                    break;
801            }
802            super.handleMessage(msg);
803        }
804    };
805}
806