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