• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Context;
20 import android.content.Intent;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.graphics.drawable.Drawable;
26 import android.hardware.SensorManager;
27 import android.os.BatteryStats;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.Parcel;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.SystemClock;
35 import android.os.BatteryStats.Uid;
36 import android.preference.Preference;
37 import android.preference.PreferenceActivity;
38 import android.preference.PreferenceGroup;
39 import android.preference.PreferenceScreen;
40 import android.util.Log;
41 import android.util.SparseArray;
42 import android.view.Menu;
43 import android.view.MenuItem;
44 
45 import com.android.internal.app.IBatteryStats;
46 import com.android.internal.os.BatteryStatsImpl;
47 import com.android.internal.os.PowerProfile;
48 import com.android.settings.R;
49 import com.android.settings.fuelgauge.PowerUsageDetail.DrainType;
50 
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 
57 /**
58  * Displays a list of apps and subsystems that consume power, ordered by how much power was
59  * consumed since the last time it was unplugged.
60  */
61 public class PowerUsageSummary extends PreferenceActivity implements Runnable {
62 
63     private static final boolean DEBUG = false;
64 
65     private static final String TAG = "PowerUsageSummary";
66 
67     private static final int MENU_STATS_TYPE = Menu.FIRST;
68     private static final int MENU_STATS_REFRESH = Menu.FIRST + 1;
69 
70     IBatteryStats mBatteryInfo;
71     BatteryStatsImpl mStats;
72     private List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
73 
74     private PreferenceGroup mAppListGroup;
75 
76     private int mStatsType = BatteryStats.STATS_UNPLUGGED;
77 
78     private static final int MIN_POWER_THRESHOLD = 5;
79     private static final int MAX_ITEMS_TO_LIST = 10;
80 
81     private double mMaxPower = 1;
82     private double mTotalPower;
83     private PowerProfile mPowerProfile;
84 
85     private HashMap<String,UidToDetail> mUidCache = new HashMap<String,UidToDetail>();
86 
87     /** Queue for fetching name and icon for an application */
88     private ArrayList<BatterySipper> mRequestQueue = new ArrayList<BatterySipper>();
89     private Thread mRequestThread;
90     private boolean mAbort;
91 
92     static class UidToDetail {
93         String name;
94         String packageName;
95         Drawable icon;
96     }
97 
98     @Override
onCreate(Bundle icicle)99     protected void onCreate(Bundle icicle) {
100         super.onCreate(icicle);
101 
102         addPreferencesFromResource(R.xml.power_usage_summary);
103         mBatteryInfo = IBatteryStats.Stub.asInterface(
104                 ServiceManager.getService("batteryinfo"));
105         mAppListGroup = (PreferenceGroup) findPreference("app_list");
106         mPowerProfile = new PowerProfile(this);
107     }
108 
109     @Override
onResume()110     protected void onResume() {
111         super.onResume();
112         mAbort = false;
113         refreshStats();
114     }
115 
116     @Override
onPause()117     protected void onPause() {
118         synchronized (mRequestQueue) {
119             mAbort = true;
120         }
121         mHandler.removeMessages(MSG_UPDATE_NAME_ICON);
122         super.onPause();
123     }
124 
125     @Override
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)126     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
127         PowerGaugePreference pgp = (PowerGaugePreference) preference;
128         BatterySipper sipper = pgp.getInfo();
129         Intent intent = new Intent(this, PowerUsageDetail.class);
130         intent.putExtra(PowerUsageDetail.EXTRA_TITLE, sipper.name);
131         intent.putExtra(PowerUsageDetail.EXTRA_PERCENT, (int)
132                 Math.ceil(sipper.getSortValue() * 100 / mTotalPower));
133         intent.putExtra(PowerUsageDetail.EXTRA_GAUGE, (int)
134                 Math.ceil(sipper.getSortValue() * 100 / mMaxPower));
135         intent.putExtra(PowerUsageDetail.EXTRA_ICON_PACKAGE, sipper.defaultPackageName);
136         intent.putExtra(PowerUsageDetail.EXTRA_ICON_ID, sipper.iconId);
137         intent.putExtra(PowerUsageDetail.EXTRA_NO_COVERAGE, sipper.noCoveragePercent);
138         if (sipper.uidObj != null) {
139             intent.putExtra(PowerUsageDetail.EXTRA_UID, sipper.uidObj.getUid());
140         }
141         intent.putExtra(PowerUsageDetail.EXTRA_DRAIN_TYPE, sipper.drainType);
142 
143         int[] types;
144         double[] values;
145         switch (sipper.drainType) {
146             case APP:
147             {
148                 Uid uid = sipper.uidObj;
149                 types = new int[] {
150                     R.string.usage_type_cpu,
151                     R.string.usage_type_cpu_foreground,
152                     R.string.usage_type_gps,
153                     R.string.usage_type_data_send,
154                     R.string.usage_type_data_recv,
155                     R.string.usage_type_audio,
156                     R.string.usage_type_video,
157                 };
158                 values = new double[] {
159                     sipper.cpuTime,
160                     sipper.cpuFgTime,
161                     sipper.gpsTime,
162                     uid != null? uid.getTcpBytesSent(mStatsType) : 0,
163                     uid != null? uid.getTcpBytesReceived(mStatsType) : 0,
164                     0,
165                     0
166                 };
167 
168             }
169             break;
170             case CELL:
171             {
172                 types = new int[] {
173                     R.string.usage_type_on_time,
174                     R.string.usage_type_no_coverage
175                 };
176                 values = new double[] {
177                     sipper.usageTime,
178                     sipper.noCoveragePercent
179                 };
180             }
181             break;
182             default:
183             {
184                 types = new int[] {
185                     R.string.usage_type_on_time
186                 };
187                 values = new double[] {
188                     sipper.usageTime
189                 };
190             }
191         }
192         intent.putExtra(PowerUsageDetail.EXTRA_DETAIL_TYPES, types);
193         intent.putExtra(PowerUsageDetail.EXTRA_DETAIL_VALUES, values);
194         startActivity(intent);
195 
196         return super.onPreferenceTreeClick(preferenceScreen, preference);
197     }
198 
199     @Override
onCreateOptionsMenu(Menu menu)200     public boolean onCreateOptionsMenu(Menu menu) {
201         if (DEBUG) {
202             menu.add(0, MENU_STATS_TYPE, 0, R.string.menu_stats_total)
203                     .setIcon(com.android.internal.R.drawable.ic_menu_info_details)
204                     .setAlphabeticShortcut('t');
205         }
206         menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh)
207                 .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
208                 .setAlphabeticShortcut('r');
209         return true;
210     }
211 
212     @Override
onPrepareOptionsMenu(Menu menu)213     public boolean onPrepareOptionsMenu(Menu menu) {
214         if (DEBUG) {
215             menu.findItem(MENU_STATS_TYPE).setTitle(mStatsType == BatteryStats.STATS_TOTAL
216                     ? R.string.menu_stats_unplugged
217                     : R.string.menu_stats_total);
218         }
219         return true;
220     }
221 
222     @Override
onOptionsItemSelected(MenuItem item)223     public boolean onOptionsItemSelected(MenuItem item) {
224         switch (item.getItemId()) {
225             case MENU_STATS_TYPE:
226                 if (mStatsType == BatteryStats.STATS_TOTAL) {
227                     mStatsType = BatteryStats.STATS_UNPLUGGED;
228                 } else {
229                     mStatsType = BatteryStats.STATS_TOTAL;
230                 }
231                 refreshStats();
232                 return true;
233             case MENU_STATS_REFRESH:
234                 mStats = null;
235                 refreshStats();
236                 return true;
237             default:
238                 return false;
239         }
240     }
241 
refreshStats()242     private void refreshStats() {
243         if (mStats == null) {
244             load();
245         }
246         mMaxPower = 0;
247         mTotalPower = 0;
248 
249         mAppListGroup.removeAll();
250         mUsageList.clear();
251         processAppUsage();
252         processMiscUsage();
253 
254         mAppListGroup.setOrderingAsAdded(false);
255 
256         Collections.sort(mUsageList);
257         for (BatterySipper sipper : mUsageList) {
258             if (sipper.getSortValue() < MIN_POWER_THRESHOLD) continue;
259             final double percentOfTotal =  ((sipper.getSortValue() / mTotalPower) * 100);
260             if (percentOfTotal < 1) continue;
261             PowerGaugePreference pref = new PowerGaugePreference(this, sipper.getIcon(), sipper);
262             double percentOfMax = (sipper.getSortValue() * 100) / mMaxPower;
263             sipper.percent = percentOfTotal;
264             pref.setTitle(sipper.name);
265             pref.setPercent(percentOfTotal);
266             pref.setOrder(Integer.MAX_VALUE - (int) sipper.getSortValue()); // Invert the order
267             pref.setGaugeValue(percentOfMax);
268             if (sipper.uidObj != null) {
269                 pref.setKey(Integer.toString(sipper.uidObj.getUid()));
270             }
271             mAppListGroup.addPreference(pref);
272             if (mAppListGroup.getPreferenceCount() > MAX_ITEMS_TO_LIST) break;
273         }
274         if (DEBUG) setTitle("Battery total uAh = " + ((mTotalPower * 1000) / 3600));
275         synchronized (mRequestQueue) {
276             if (!mRequestQueue.isEmpty()) {
277                 if (mRequestThread == null) {
278                     mRequestThread = new Thread(this, "BatteryUsage Icon Loader");
279                     mRequestThread.setPriority(Thread.MIN_PRIORITY);
280                     mRequestThread.start();
281                 }
282                 mRequestQueue.notify();
283             }
284         }
285     }
286 
updateStatsPeriod(long duration)287     private void updateStatsPeriod(long duration) {
288         String durationString = Utils.formatElapsedTime(this, duration / 1000);
289         String label = getString(mStats.isOnBattery()
290                 ? R.string.battery_stats_duration
291                 : R.string.battery_stats_last_duration, durationString);
292         setTitle(label);
293     }
294 
processAppUsage()295     private void processAppUsage() {
296         SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
297         final int which = mStatsType;
298         final int speedSteps = mPowerProfile.getNumSpeedSteps();
299         final double[] powerCpuNormal = new double[speedSteps];
300         final long[] cpuSpeedStepTimes = new long[speedSteps];
301         for (int p = 0; p < speedSteps; p++) {
302             powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
303         }
304         final double averageCostPerByte = getAverageDataCost();
305         long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which);
306         updateStatsPeriod(uSecTime);
307         SparseArray<? extends Uid> uidStats = mStats.getUidStats();
308         final int NU = uidStats.size();
309         for (int iu = 0; iu < NU; iu++) {
310             Uid u = uidStats.valueAt(iu);
311             double power = 0;
312             double highestDrain = 0;
313             String packageWithHighestDrain = null;
314             //mUsageList.add(new AppUsage(u.getUid(), new double[] {power}));
315             Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
316             long cpuTime = 0;
317             long cpuFgTime = 0;
318             long gpsTime = 0;
319             if (processStats.size() > 0) {
320                 // Process CPU time
321                 for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
322                         : processStats.entrySet()) {
323                     if (DEBUG) Log.i(TAG, "Process name = " + ent.getKey());
324                     Uid.Proc ps = ent.getValue();
325                     final long userTime = ps.getUserTime(which);
326                     final long systemTime = ps.getSystemTime(which);
327                     final long foregroundTime = ps.getForegroundTime(which);
328                     cpuFgTime += foregroundTime * 10; // convert to millis
329                     final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
330                     int totalTimeAtSpeeds = 0;
331                     // Get the total first
332                     for (int step = 0; step < speedSteps; step++) {
333                         cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
334                         totalTimeAtSpeeds += cpuSpeedStepTimes[step];
335                     }
336                     if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
337                     // Then compute the ratio of time spent at each speed
338                     double processPower = 0;
339                     for (int step = 0; step < speedSteps; step++) {
340                         double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
341                         processPower += ratio * tmpCpuTime * powerCpuNormal[step];
342                     }
343                     cpuTime += tmpCpuTime;
344                     power += processPower;
345                     if (highestDrain < processPower) {
346                         highestDrain = processPower;
347                         packageWithHighestDrain = ent.getKey();
348                     }
349 
350                 }
351                 if (DEBUG) Log.i(TAG, "Max drain of " + highestDrain
352                         + " by " + packageWithHighestDrain);
353             }
354             if (cpuFgTime > cpuTime) {
355                 if (DEBUG && cpuFgTime > cpuTime + 10000) {
356                     Log.i(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
357                 }
358                 cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
359             }
360             power /= 1000;
361 
362             // Add cost of data traffic
363             power += (u.getTcpBytesReceived(mStatsType) + u.getTcpBytesSent(mStatsType))
364                     * averageCostPerByte;
365 
366             // Process Sensor usage
367             Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
368             for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
369                     : sensorStats.entrySet()) {
370                 Uid.Sensor sensor = sensorEntry.getValue();
371                 int sensorType = sensor.getHandle();
372                 BatteryStats.Timer timer = sensor.getSensorTime();
373                 long sensorTime = timer.getTotalTimeLocked(uSecTime, which) / 1000;
374                 double multiplier = 0;
375                 switch (sensorType) {
376                     case Uid.Sensor.GPS:
377                         multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
378                         gpsTime = sensorTime;
379                         break;
380                     default:
381                         android.hardware.Sensor sensorData =
382                                 sensorManager.getDefaultSensor(sensorType);
383                         if (sensorData != null) {
384                             multiplier = sensorData.getPower();
385                             if (DEBUG) {
386                                 Log.i(TAG, "Got sensor " + sensorData.getName() + " with power = "
387                                         + multiplier);
388                             }
389                         }
390                 }
391                 power += (multiplier * sensorTime) / 1000;
392             }
393 
394             // Add the app to the list if it is consuming power
395             if (power != 0) {
396                 BatterySipper app = new BatterySipper(packageWithHighestDrain, DrainType.APP, 0, u,
397                         new double[] {power});
398                 app.cpuTime = cpuTime;
399                 app.gpsTime = gpsTime;
400                 app.cpuFgTime = cpuFgTime;
401                 mUsageList.add(app);
402             }
403             if (power > mMaxPower) mMaxPower = power;
404             mTotalPower += power;
405             if (DEBUG) Log.i(TAG, "Added power = " + power);
406         }
407     }
408 
addPhoneUsage(long uSecNow)409     private void addPhoneUsage(long uSecNow) {
410         long phoneOnTimeMs = mStats.getPhoneOnTime(uSecNow, mStatsType) / 1000;
411         double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
412                 * phoneOnTimeMs / 1000;
413         addEntry(getString(R.string.power_phone), DrainType.PHONE, phoneOnTimeMs,
414                 R.drawable.ic_settings_voice_calls, phoneOnPower);
415     }
416 
addScreenUsage(long uSecNow)417     private void addScreenUsage(long uSecNow) {
418         double power = 0;
419         long screenOnTimeMs = mStats.getScreenOnTime(uSecNow, mStatsType) / 1000;
420         power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
421         final double screenFullPower =
422                 mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
423         for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
424             double screenBinPower = screenFullPower * (i + 0.5f)
425                     / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
426             long brightnessTime = mStats.getScreenBrightnessTime(i, uSecNow, mStatsType) / 1000;
427             power += screenBinPower * brightnessTime;
428             if (DEBUG) {
429                 Log.i(TAG, "Screen bin power = " + (int) screenBinPower + ", time = "
430                         + brightnessTime);
431             }
432         }
433         power /= 1000; // To seconds
434         addEntry(getString(R.string.power_screen), DrainType.SCREEN, screenOnTimeMs,
435                 R.drawable.ic_settings_display, power);
436     }
437 
addRadioUsage(long uSecNow)438     private void addRadioUsage(long uSecNow) {
439         double power = 0;
440         final int BINS = BatteryStats.NUM_SIGNAL_STRENGTH_BINS;
441         long signalTimeMs = 0;
442         for (int i = 0; i < BINS; i++) {
443             long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000;
444             power += strengthTimeMs / 1000
445                     * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
446             signalTimeMs += strengthTimeMs;
447         }
448         long scanningTimeMs = mStats.getPhoneSignalScanningTime(uSecNow, mStatsType) / 1000;
449         power += scanningTimeMs / 1000 * mPowerProfile.getAveragePower(
450                 PowerProfile.POWER_RADIO_SCANNING);
451         BatterySipper bs =
452                 addEntry(getString(R.string.power_cell), DrainType.CELL, signalTimeMs,
453                 R.drawable.ic_settings_cell_standby, power);
454         if (signalTimeMs != 0) {
455             bs.noCoveragePercent = mStats.getPhoneSignalStrengthTime(0, uSecNow, mStatsType)
456                     / 1000 * 100.0 / signalTimeMs;
457         }
458     }
459 
addWiFiUsage(long uSecNow)460     private void addWiFiUsage(long uSecNow) {
461         long onTimeMs = mStats.getWifiOnTime(uSecNow, mStatsType) / 1000;
462         long runningTimeMs = mStats.getWifiRunningTime(uSecNow, mStatsType) / 1000;
463         double wifiPower = (onTimeMs * 0 /* TODO */
464                 * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
465             + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / 1000;
466         addEntry(getString(R.string.power_wifi), DrainType.WIFI, runningTimeMs,
467                 R.drawable.ic_settings_wifi, wifiPower);
468     }
469 
addIdleUsage(long uSecNow)470     private void addIdleUsage(long uSecNow) {
471         long idleTimeMs = (uSecNow - mStats.getScreenOnTime(uSecNow, mStatsType)) / 1000;
472         double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
473                 / 1000;
474         addEntry(getString(R.string.power_idle), DrainType.IDLE, idleTimeMs,
475                 R.drawable.ic_settings_phone_idle, idlePower);
476     }
477 
addBluetoothUsage(long uSecNow)478     private void addBluetoothUsage(long uSecNow) {
479         long btOnTimeMs = mStats.getBluetoothOnTime(uSecNow, mStatsType) / 1000;
480         double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
481                 / 1000;
482         int btPingCount = mStats.getBluetoothPingCount();
483         btPower += (btPingCount
484                 * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD)) / 1000;
485 
486         addEntry(getString(R.string.power_bluetooth), DrainType.BLUETOOTH, btOnTimeMs,
487                 R.drawable.ic_settings_bluetooth, btPower);
488     }
489 
getAverageDataCost()490     private double getAverageDataCost() {
491         final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
492         final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
493         final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
494                 / 3600;
495         final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
496                 / 3600;
497         final long mobileData = mStats.getMobileTcpBytesReceived(mStatsType) +
498                 mStats.getMobileTcpBytesSent(mStatsType);
499         final long wifiData = mStats.getTotalTcpBytesReceived(mStatsType) +
500                 mStats.getTotalTcpBytesSent(mStatsType) - mobileData;
501         final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000;
502         final long mobileBps = radioDataUptimeMs != 0
503                 ? mobileData * 8 * 1000 / radioDataUptimeMs
504                 : MOBILE_BPS;
505 
506         double mobileCostPerByte = MOBILE_POWER / (mobileBps / 8);
507         double wifiCostPerByte = WIFI_POWER / (WIFI_BPS / 8);
508         if (wifiData + mobileData != 0) {
509             return (mobileCostPerByte * mobileData + wifiCostPerByte * wifiData)
510                     / (mobileData + wifiData);
511         } else {
512             return 0;
513         }
514     }
515 
processMiscUsage()516     private void processMiscUsage() {
517         final int which = mStatsType;
518         long uSecTime = SystemClock.elapsedRealtime() * 1000;
519         final long uSecNow = mStats.computeBatteryRealtime(uSecTime, which);
520         final long timeSinceUnplugged = uSecNow;
521         if (DEBUG) {
522             Log.i(TAG, "Uptime since last unplugged = " + (timeSinceUnplugged / 1000));
523         }
524 
525         addPhoneUsage(uSecNow);
526         addScreenUsage(uSecNow);
527         addWiFiUsage(uSecNow);
528         addBluetoothUsage(uSecNow);
529         addIdleUsage(uSecNow); // Not including cellular idle power
530         addRadioUsage(uSecNow);
531     }
532 
addEntry(String label, DrainType drainType, long time, int iconId, double power)533     private BatterySipper addEntry(String label, DrainType drainType, long time, int iconId,
534             double power) {
535         if (power > mMaxPower) mMaxPower = power;
536         mTotalPower += power;
537         BatterySipper bs = new BatterySipper(label, drainType, iconId, null, new double[] {power});
538         bs.usageTime = time;
539         bs.iconId = iconId;
540         mUsageList.add(bs);
541         return bs;
542     }
543 
load()544     private void load() {
545         try {
546             byte[] data = mBatteryInfo.getStatistics();
547             Parcel parcel = Parcel.obtain();
548             parcel.unmarshall(data, 0, data.length);
549             parcel.setDataPosition(0);
550             mStats = com.android.internal.os.BatteryStatsImpl.CREATOR
551                     .createFromParcel(parcel);
552         } catch (RemoteException e) {
553             Log.e(TAG, "RemoteException:", e);
554         }
555     }
556 
557     class BatterySipper implements Comparable<BatterySipper> {
558         String name;
559         Drawable icon;
560         int iconId; // For passing to the detail screen.
561         Uid uidObj;
562         double value;
563         double[] values;
564         DrainType drainType;
565         long usageTime;
566         long cpuTime;
567         long gpsTime;
568         long cpuFgTime;
569         double percent;
570         double noCoveragePercent;
571         String defaultPackageName;
572 
BatterySipper(String label, DrainType drainType, int iconId, Uid uid, double[] values)573         BatterySipper(String label, DrainType drainType, int iconId, Uid uid, double[] values) {
574             this.values = values;
575             name = label;
576             this.drainType = drainType;
577             if (iconId > 0) {
578                 icon = getResources().getDrawable(iconId);
579             }
580             if (values != null) value = values[0];
581             if ((label == null || iconId == 0) && uid != null) {
582                 getQuickNameIconForUid(uid);
583             }
584             uidObj = uid;
585         }
586 
getSortValue()587         double getSortValue() {
588             return value;
589         }
590 
getValues()591         double[] getValues() {
592             return values;
593         }
594 
getIcon()595         Drawable getIcon() {
596             return icon;
597         }
598 
compareTo(BatterySipper other)599         public int compareTo(BatterySipper other) {
600             // Return the flipped value because we want the items in descending order
601             return (int) (other.getSortValue() - getSortValue());
602         }
603 
getQuickNameIconForUid(Uid uidObj)604         void getQuickNameIconForUid(Uid uidObj) {
605             final int uid = uidObj.getUid();
606             final String uidString = Integer.toString(uid);
607             if (mUidCache.containsKey(uidString)) {
608                 UidToDetail utd = mUidCache.get(uidString);
609                 defaultPackageName = utd.packageName;
610                 name = utd.name;
611                 icon = utd.icon;
612                 return;
613             }
614             PackageManager pm = getPackageManager();
615             final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
616             String[] packages = pm.getPackagesForUid(uid);
617             icon = pm.getDefaultActivityIcon();
618             if (packages == null) {
619                 //name = Integer.toString(uid);
620                 if (uid == 0) {
621                     name = getResources().getString(R.string.process_kernel_label);
622                 } else if ("mediaserver".equals(name)) {
623                     name = getResources().getString(R.string.process_mediaserver_label);
624                 }
625                 iconId = R.drawable.ic_power_system;
626                 icon = getResources().getDrawable(iconId);
627                 return;
628             } else {
629                 //name = packages[0];
630             }
631             synchronized (mRequestQueue) {
632                 mRequestQueue.add(this);
633             }
634         }
635 
636         /**
637          * Sets name and icon
638          * @param uid Uid of the application
639          */
getNameIcon()640         void getNameIcon() {
641             PackageManager pm = getPackageManager();
642             final int uid = uidObj.getUid();
643             final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
644             String[] packages = pm.getPackagesForUid(uid);
645             if (packages == null) {
646                 name = Integer.toString(uid);
647                 return;
648             }
649 
650             String[] packageLabels = new String[packages.length];
651             System.arraycopy(packages, 0, packageLabels, 0, packages.length);
652 
653             int preferredIndex = -1;
654             // Convert package names to user-facing labels where possible
655             for (int i = 0; i < packageLabels.length; i++) {
656                 // Check if package matches preferred package
657                 if (packageLabels[i].equals(name)) preferredIndex = i;
658                 try {
659                     ApplicationInfo ai = pm.getApplicationInfo(packageLabels[i], 0);
660                     CharSequence label = ai.loadLabel(pm);
661                     if (label != null) {
662                         packageLabels[i] = label.toString();
663                     }
664                     if (ai.icon != 0) {
665                         defaultPackageName = packages[i];
666                         icon = ai.loadIcon(pm);
667                         break;
668                     }
669                 } catch (NameNotFoundException e) {
670                 }
671             }
672             if (icon == null) icon = defaultActivityIcon;
673 
674             if (packageLabels.length == 1) {
675                 name = packageLabels[0];
676             } else {
677                 // Look for an official name for this UID.
678                 for (String pkgName : packages) {
679                     try {
680                         final PackageInfo pi = pm.getPackageInfo(pkgName, 0);
681                         if (pi.sharedUserLabel != 0) {
682                             final CharSequence nm = pm.getText(pkgName,
683                                     pi.sharedUserLabel, pi.applicationInfo);
684                             if (nm != null) {
685                                 name = nm.toString();
686                                 if (pi.applicationInfo.icon != 0) {
687                                     defaultPackageName = pkgName;
688                                     icon = pi.applicationInfo.loadIcon(pm);
689                                 }
690                                 break;
691                             }
692                         }
693                     } catch (PackageManager.NameNotFoundException e) {
694                     }
695                 }
696             }
697             final String uidString = Integer.toString(uidObj.getUid());
698             UidToDetail utd = new UidToDetail();
699             utd.name = name;
700             utd.icon = icon;
701             utd.packageName = defaultPackageName;
702             mUidCache.put(uidString, utd);
703             mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_NAME_ICON, this));
704         }
705     }
706 
run()707     public void run() {
708         while (true) {
709             BatterySipper bs;
710             synchronized (mRequestQueue) {
711                 if (mRequestQueue.isEmpty() || mAbort) {
712                     mRequestThread = null;
713                     return;
714                 }
715                 bs = mRequestQueue.remove(0);
716             }
717             bs.getNameIcon();
718         }
719     }
720 
721     private static final int MSG_UPDATE_NAME_ICON = 1;
722 
723     Handler mHandler = new Handler() {
724 
725         @Override
726         public void handleMessage(Message msg) {
727             switch (msg.what) {
728                 case MSG_UPDATE_NAME_ICON:
729                     BatterySipper bs = (BatterySipper) msg.obj;
730                     PowerGaugePreference pgp =
731                             (PowerGaugePreference) findPreference(
732                                     Integer.toString(bs.uidObj.getUid()));
733                     if (pgp != null) {
734                         pgp.setIcon(bs.icon);
735                         pgp.setPercent(bs.percent);
736                         pgp.setTitle(bs.name);
737                     }
738                     break;
739             }
740             super.handleMessage(msg);
741         }
742     };
743 }
744