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