• 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.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
onCreate(Bundle icicle)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
onResume()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
onPause()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
onDestroy()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
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)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
onCreateOptionsMenu(Menu menu, MenuInflater inflater)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
onOptionsItemSelected(MenuItem item)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 
addNotAvailableMessage()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 
refreshStats()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 
processAppUsage()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 
addPhoneUsage(long uSecNow)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 
addScreenUsage(long uSecNow)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 
addRadioUsage(long uSecNow)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 
aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag)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 
addWiFiUsage(long uSecNow)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 
addIdleUsage(long uSecNow)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 
addBluetoothUsage(long uSecNow)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 
addUserUsage()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 
getAverageDataCost()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 
processMiscUsage()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 
addEntry(String label, DrainType drainType, long time, int iconId, double power)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 
load()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 
run()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