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