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