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