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