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