BatteryStatsHelper.java revision ae19a06e030e55b4db3cb20f1e564d49a78a395e
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 17package com.android.internal.os; 18 19import static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA; 20import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA; 21import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA; 22import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA; 23 24import android.content.Context; 25import android.hardware.Sensor; 26import android.hardware.SensorManager; 27import android.net.ConnectivityManager; 28import android.os.BatteryStats; 29import android.os.BatteryStats.Uid; 30import android.os.Bundle; 31import android.os.Parcel; 32import android.os.Process; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.os.SystemClock; 36import android.os.UserHandle; 37import android.telephony.SignalStrength; 38import android.util.Log; 39import android.util.SparseArray; 40 41import com.android.internal.app.IBatteryStats; 42import com.android.internal.os.BatterySipper.DrainType; 43 44import java.io.PrintWriter; 45import java.util.ArrayList; 46import java.util.Collections; 47import java.util.List; 48import java.util.Map; 49 50/** 51 * A helper class for retrieving the power usage information for all applications and services. 52 * 53 * The caller must initialize this class as soon as activity object is ready to use (for example, in 54 * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). 55 */ 56public class BatteryStatsHelper { 57 58 private static final boolean DEBUG = true; 59 60 private static final String TAG = BatteryStatsHelper.class.getSimpleName(); 61 62 private static BatteryStats sStatsXfer; 63 64 final private Context mContext; 65 66 private IBatteryStats mBatteryInfo; 67 private BatteryStats mStats; 68 private PowerProfile mPowerProfile; 69 70 private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>(); 71 private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>(); 72 private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>(); 73 private final SparseArray<List<BatterySipper>> mUserSippers 74 = new SparseArray<List<BatterySipper>>(); 75 private final SparseArray<Double> mUserPower = new SparseArray<Double>(); 76 77 private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; 78 private int mAsUser = 0; 79 80 long mBatteryRealtime; 81 long mBatteryUptime; 82 long mTypeBatteryRealtime; 83 long mTypeBatteryUptime; 84 85 private long mStatsPeriod = 0; 86 private double mMaxPower = 1; 87 private double mTotalPower; 88 private double mWifiPower; 89 private double mBluetoothPower; 90 private double mMinDrainedPower; 91 private double mMaxDrainedPower; 92 93 // How much the apps together have left WIFI running. 94 private long mAppWifiRunning; 95 96 public BatteryStatsHelper(Context context) { 97 mContext = context; 98 } 99 100 /** Clears the current stats and forces recreating for future use. */ 101 public void clearStats() { 102 mStats = null; 103 } 104 105 public BatteryStats getStats() { 106 if (mStats == null) { 107 load(); 108 } 109 return mStats; 110 } 111 112 public PowerProfile getPowerProfile() { 113 return mPowerProfile; 114 } 115 116 public void create(BatteryStats stats) { 117 mPowerProfile = new PowerProfile(mContext); 118 mStats = stats; 119 } 120 121 public void create(Bundle icicle) { 122 if (icicle != null) { 123 mStats = sStatsXfer; 124 } 125 mBatteryInfo = IBatteryStats.Stub.asInterface( 126 ServiceManager.getService(BatteryStats.SERVICE_NAME)); 127 mPowerProfile = new PowerProfile(mContext); 128 } 129 130 public void storeState() { 131 sStatsXfer = mStats; 132 } 133 134 public static String makemAh(double power) { 135 if (power < .0001) return String.format("%.8f", power); 136 else if (power < .0001) return String.format("%.7f", power); 137 else if (power < .001) return String.format("%.6f", power); 138 else if (power < .01) return String.format("%.5f", power); 139 else if (power < .1) return String.format("%.4f", power); 140 else if (power < 1) return String.format("%.3f", power); 141 else if (power < 10) return String.format("%.2f", power); 142 else if (power < 100) return String.format("%.1f", power); 143 else return String.format("%.0f", power); 144 } 145 146 /** 147 * Refreshes the power usage list. 148 */ 149 public void refreshStats(int statsType, int asUser) { 150 refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000, 151 SystemClock.uptimeMillis() * 1000); 152 } 153 154 public void refreshStats(int statsType, int asUser, long rawRealtimeNano, long rawUptimeNano) { 155 // Initialize mStats if necessary. 156 getStats(); 157 158 mMaxPower = 0; 159 mTotalPower = 0; 160 mWifiPower = 0; 161 mBluetoothPower = 0; 162 mAppWifiRunning = 0; 163 164 mUsageList.clear(); 165 mWifiSippers.clear(); 166 mBluetoothSippers.clear(); 167 mUserSippers.clear(); 168 mUserPower.clear(); 169 170 if (mStats == null) { 171 return; 172 } 173 174 mStatsType = statsType; 175 mAsUser = asUser; 176 mBatteryUptime = mStats.getBatteryUptime(rawUptimeNano); 177 mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeNano); 178 mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeNano, mStatsType); 179 mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeNano, mStatsType); 180 181 if (DEBUG) { 182 Log.d(TAG, "Raw time: realtime=" + (rawRealtimeNano/1000) + " uptime=" 183 + (rawUptimeNano/1000)); 184 Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime=" 185 + (mBatteryUptime/1000)); 186 Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime=" 187 + (mTypeBatteryUptime/1000)); 188 } 189 mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge() 190 * mPowerProfile.getBatteryCapacity()) / 100; 191 mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() 192 * mPowerProfile.getBatteryCapacity()) / 100; 193 194 processAppUsage(); 195 processMiscUsage(); 196 197 if (DEBUG) { 198 Log.d(TAG, "Accuracy: total computed=" + makemAh(mTotalPower) + ", min discharge=" 199 + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower)); 200 } 201 if (true || mStats.getLowDischargeAmountSinceCharge() > 10) { 202 if (mMinDrainedPower > mTotalPower) { 203 double amount = mMinDrainedPower - mTotalPower; 204 if (mMaxPower < amount) { 205 mMaxPower = amount; 206 } 207 addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount); 208 } else if (mMaxDrainedPower < mTotalPower) { 209 double amount = mTotalPower - mMaxDrainedPower; 210 if (mMaxPower < amount) { 211 mMaxPower = amount; 212 } 213 addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount); 214 } 215 } 216 217 Collections.sort(mUsageList); 218 } 219 220 private void processAppUsage() { 221 SensorManager sensorManager = (SensorManager) mContext.getSystemService( 222 Context.SENSOR_SERVICE); 223 final int which = mStatsType; 224 final int speedSteps = mPowerProfile.getNumSpeedSteps(); 225 final double[] powerCpuNormal = new double[speedSteps]; 226 final long[] cpuSpeedStepTimes = new long[speedSteps]; 227 for (int p = 0; p < speedSteps; p++) { 228 powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p); 229 } 230 final double mobilePowerPerPacket = getMobilePowerPerPacket(); 231 final double wifiPowerPerPacket = getWifiPowerPerPacket(); 232 long appWakelockTime = 0; 233 BatterySipper osApp = null; 234 mStatsPeriod = mTypeBatteryRealtime; 235 SparseArray<? extends Uid> uidStats = mStats.getUidStats(); 236 final int NU = uidStats.size(); 237 for (int iu = 0; iu < NU; iu++) { 238 Uid u = uidStats.valueAt(iu); 239 double p; // in mAs 240 double power = 0; // in mAs 241 double highestDrain = 0; 242 String packageWithHighestDrain = null; 243 Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats(); 244 long cpuTime = 0; 245 long cpuFgTime = 0; 246 long wakelockTime = 0; 247 long gpsTime = 0; 248 if (processStats.size() > 0) { 249 // Process CPU time 250 for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent 251 : processStats.entrySet()) { 252 Uid.Proc ps = ent.getValue(); 253 final long userTime = ps.getUserTime(which); 254 final long systemTime = ps.getSystemTime(which); 255 final long foregroundTime = ps.getForegroundTime(which); 256 cpuFgTime += foregroundTime * 10; // convert to millis 257 final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis 258 int totalTimeAtSpeeds = 0; 259 // Get the total first 260 for (int step = 0; step < speedSteps; step++) { 261 cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which); 262 totalTimeAtSpeeds += cpuSpeedStepTimes[step]; 263 } 264 if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1; 265 // Then compute the ratio of time spent at each speed 266 double processPower = 0; 267 for (int step = 0; step < speedSteps; step++) { 268 double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds; 269 if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #" 270 + step + " ratio=" + makemAh(ratio) + " power=" 271 + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000))); 272 processPower += ratio * tmpCpuTime * powerCpuNormal[step]; 273 } 274 cpuTime += tmpCpuTime; 275 if (DEBUG && processPower != 0) { 276 Log.d(TAG, String.format("process %s, cpu power=%s", 277 ent.getKey(), makemAh(processPower / (60*60*1000)))); 278 } 279 power += processPower; 280 if (packageWithHighestDrain == null 281 || packageWithHighestDrain.startsWith("*")) { 282 highestDrain = processPower; 283 packageWithHighestDrain = ent.getKey(); 284 } else if (highestDrain < processPower 285 && !ent.getKey().startsWith("*")) { 286 highestDrain = processPower; 287 packageWithHighestDrain = ent.getKey(); 288 } 289 } 290 } 291 if (cpuFgTime > cpuTime) { 292 if (DEBUG && cpuFgTime > cpuTime + 10000) { 293 Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time"); 294 } 295 cpuTime = cpuFgTime; // Statistics may not have been gathered yet. 296 } 297 power /= (60*60*1000); 298 299 // Process wake lock usage 300 Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats(); 301 for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry 302 : wakelockStats.entrySet()) { 303 Uid.Wakelock wakelock = wakelockEntry.getValue(); 304 // Only care about partial wake locks since full wake locks 305 // are canceled when the user turns the screen off. 306 BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL); 307 if (timer != null) { 308 wakelockTime += timer.getTotalTimeLocked(mBatteryRealtime, which); 309 } 310 } 311 wakelockTime /= 1000; // convert to millis 312 appWakelockTime += wakelockTime; 313 314 // Add cost of holding a wake lock 315 p = (wakelockTime 316 * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000); 317 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake " 318 + wakelockTime + " power=" + makemAh(p)); 319 power += p; 320 321 // Add cost of mobile traffic 322 final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); 323 final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); 324 final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType); 325 final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType); 326 p = (mobileRx + mobileTx) * mobilePowerPerPacket; 327 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets " 328 + (mobileRx+mobileTx) + " power=" + makemAh(p)); 329 power += p; 330 331 // Add cost of wifi traffic 332 final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType); 333 final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType); 334 final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType); 335 final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType); 336 p = (wifiRx + wifiTx) * wifiPowerPerPacket; 337 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets " 338 + (mobileRx+mobileTx) + " power=" + makemAh(p)); 339 power += p; 340 341 // Add cost of keeping WIFI running. 342 long wifiRunningTimeMs = u.getWifiRunningTime(mBatteryRealtime, which) / 1000; 343 mAppWifiRunning += wifiRunningTimeMs; 344 p = (wifiRunningTimeMs 345 * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000); 346 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running " 347 + wifiRunningTimeMs + " power=" + makemAh(p)); 348 power += p; 349 350 // Add cost of WIFI scans 351 long wifiScanTimeMs = u.getWifiScanTime(mBatteryRealtime, which) / 1000; 352 p = (wifiScanTimeMs 353 * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000); 354 if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs 355 + " power=" + makemAh(p)); 356 power += p; 357 for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { 358 long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mBatteryRealtime, which) / 1000; 359 p = ((batchScanTimeMs 360 * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin)) 361 ) / (60*60*1000); 362 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin 363 + " time=" + batchScanTimeMs + " power=" + makemAh(p)); 364 power += p; 365 } 366 367 // Process Sensor usage 368 Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); 369 for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry 370 : sensorStats.entrySet()) { 371 Uid.Sensor sensor = sensorEntry.getValue(); 372 int sensorHandle = sensor.getHandle(); 373 BatteryStats.Timer timer = sensor.getSensorTime(); 374 long sensorTime = timer.getTotalTimeLocked(mBatteryRealtime, which) / 1000; 375 double multiplier = 0; 376 switch (sensorHandle) { 377 case Uid.Sensor.GPS: 378 multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON); 379 gpsTime = sensorTime; 380 break; 381 default: 382 List<Sensor> sensorList = sensorManager.getSensorList( 383 android.hardware.Sensor.TYPE_ALL); 384 for (android.hardware.Sensor s : sensorList) { 385 if (s.getHandle() == sensorHandle) { 386 multiplier = s.getPower(); 387 break; 388 } 389 } 390 } 391 p = (multiplier * sensorTime) / (60*60*1000); 392 if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle 393 + " time=" + sensorTime + " power=" + makemAh(p)); 394 power += p; 395 } 396 397 if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s", 398 u.getUid(), makemAh(power))); 399 400 // Add the app to the list if it is consuming power 401 final int userId = UserHandle.getUserId(u.getUid()); 402 if (power != 0 || u.getUid() == 0) { 403 BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 404 new double[] {power}); 405 app.cpuTime = cpuTime; 406 app.gpsTime = gpsTime; 407 app.wifiRunningTime = wifiRunningTimeMs; 408 app.cpuFgTime = cpuFgTime; 409 app.wakeLockTime = wakelockTime; 410 app.mobileRxPackets = mobileRx; 411 app.mobileTxPackets = mobileTx; 412 app.wifiRxPackets = wifiRx; 413 app.wifiTxPackets = wifiTx; 414 app.mobileRxBytes = mobileRxB; 415 app.mobileTxBytes = mobileTxB; 416 app.wifiRxBytes = wifiRxB; 417 app.wifiTxBytes = wifiTxB; 418 app.packageWithHighestDrain = packageWithHighestDrain; 419 if (u.getUid() == Process.WIFI_UID) { 420 mWifiSippers.add(app); 421 mWifiPower += power; 422 } else if (u.getUid() == Process.BLUETOOTH_UID) { 423 mBluetoothSippers.add(app); 424 mBluetoothPower += power; 425 } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser 426 && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) { 427 List<BatterySipper> list = mUserSippers.get(userId); 428 if (list == null) { 429 list = new ArrayList<BatterySipper>(); 430 mUserSippers.put(userId, list); 431 } 432 list.add(app); 433 if (power != 0) { 434 Double userPower = mUserPower.get(userId); 435 if (userPower == null) { 436 userPower = power; 437 } else { 438 userPower += power; 439 } 440 mUserPower.put(userId, userPower); 441 } 442 } else { 443 mUsageList.add(app); 444 if (power > mMaxPower) mMaxPower = power; 445 mTotalPower += power; 446 } 447 if (u.getUid() == 0) { 448 osApp = app; 449 } 450 } 451 } 452 453 // The device has probably been awake for longer than the screen on 454 // time and application wake lock time would account for. Assign 455 // this remainder to the OS, if possible. 456 if (osApp != null) { 457 long wakeTimeMillis = mBatteryUptime / 1000; 458 wakeTimeMillis -= appWakelockTime 459 + (mStats.getScreenOnTime(mBatteryRealtime, which) / 1000); 460 if (wakeTimeMillis > 0) { 461 double power = (wakeTimeMillis 462 * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) 463 / (60*60*1000); 464 if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " 465 + makemAh(power)); 466 osApp.wakeLockTime += wakeTimeMillis; 467 osApp.value += power; 468 osApp.values[0] += power; 469 if (osApp.value > mMaxPower) mMaxPower = osApp.value; 470 mTotalPower += power; 471 } 472 } 473 } 474 475 private void addPhoneUsage() { 476 long phoneOnTimeMs = mStats.getPhoneOnTime(mBatteryRealtime, mStatsType) / 1000; 477 double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) 478 * phoneOnTimeMs / (60*60*1000); 479 addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower); 480 } 481 482 private void addScreenUsage() { 483 double power = 0; 484 long screenOnTimeMs = mStats.getScreenOnTime(mBatteryRealtime, mStatsType) / 1000; 485 power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON); 486 final double screenFullPower = 487 mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL); 488 for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { 489 double screenBinPower = screenFullPower * (i + 0.5f) 490 / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; 491 long brightnessTime = mStats.getScreenBrightnessTime(i, mBatteryRealtime, mStatsType) 492 / 1000; 493 double p = screenBinPower*brightnessTime; 494 if (DEBUG && p != 0) { 495 Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime 496 + " power=" + makemAh(p/(60*60*1000))); 497 } 498 power += p; 499 } 500 power /= (60*60*1000); // To hours 501 addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power); 502 } 503 504 private void addRadioUsage() { 505 double power = 0; 506 final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; 507 long signalTimeMs = 0; 508 long noCoverageTimeMs = 0; 509 for (int i = 0; i < BINS; i++) { 510 long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mBatteryRealtime, mStatsType) 511 / 1000; 512 double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i)) 513 / (60*60*1000); 514 if (DEBUG && p != 0) { 515 Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" 516 + makemAh(p)); 517 } 518 power += p; 519 signalTimeMs += strengthTimeMs; 520 if (i == 0) { 521 noCoverageTimeMs = strengthTimeMs; 522 } 523 } 524 long scanningTimeMs = mStats.getPhoneSignalScanningTime(mBatteryRealtime, mStatsType) 525 / 1000; 526 double p = (scanningTimeMs * mPowerProfile.getAveragePower( 527 PowerProfile.POWER_RADIO_SCANNING)) 528 / (60*60*1000); 529 if (DEBUG && p != 0) { 530 Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p)); 531 } 532 power += p; 533 BatterySipper bs = 534 addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power); 535 if (signalTimeMs != 0) { 536 bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; 537 } 538 } 539 540 private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) { 541 for (int i=0; i<from.size(); i++) { 542 BatterySipper wbs = from.get(i); 543 if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime); 544 bs.cpuTime += wbs.cpuTime; 545 bs.gpsTime += wbs.gpsTime; 546 bs.wifiRunningTime += wbs.wifiRunningTime; 547 bs.cpuFgTime += wbs.cpuFgTime; 548 bs.wakeLockTime += wbs.wakeLockTime; 549 bs.mobileRxPackets += wbs.mobileRxPackets; 550 bs.mobileTxPackets += wbs.mobileTxPackets; 551 bs.wifiRxPackets += wbs.wifiRxPackets; 552 bs.wifiTxPackets += wbs.wifiTxPackets; 553 bs.mobileRxBytes += wbs.mobileRxBytes; 554 bs.mobileTxBytes += wbs.mobileTxBytes; 555 bs.wifiRxBytes += wbs.wifiRxBytes; 556 bs.wifiTxBytes += wbs.wifiTxBytes; 557 } 558 } 559 560 private void addWiFiUsage() { 561 long onTimeMs = mStats.getWifiOnTime(mBatteryRealtime, mStatsType) / 1000; 562 long runningTimeMs = mStats.getGlobalWifiRunningTime(mBatteryRealtime, mStatsType) / 1000; 563 if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs 564 + " app runningTime=" + mAppWifiRunning); 565 runningTimeMs -= mAppWifiRunning; 566 if (runningTimeMs < 0) runningTimeMs = 0; 567 double wifiPower = (onTimeMs * 0 /* TODO */ 568 * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON) 569 + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) 570 / (60*60*1000); 571 if (DEBUG && wifiPower != 0) { 572 Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower)); 573 } 574 BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs, 575 wifiPower + mWifiPower); 576 aggregateSippers(bs, mWifiSippers, "WIFI"); 577 } 578 579 private void addIdleUsage() { 580 long idleTimeMs = (mTypeBatteryRealtime 581 - mStats.getScreenOnTime(mBatteryRealtime, mStatsType)) / 1000; 582 double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)) 583 / (60*60*1000); 584 if (DEBUG && idlePower != 0) { 585 Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower)); 586 } 587 addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower); 588 } 589 590 private void addBluetoothUsage() { 591 long btOnTimeMs = mStats.getBluetoothOnTime(mBatteryRealtime, mStatsType) / 1000; 592 double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON) 593 / (60*60*1000); 594 if (DEBUG && btPower != 0) { 595 Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower)); 596 } 597 int btPingCount = mStats.getBluetoothPingCount(); 598 double pingPower = (btPingCount 599 * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD)) 600 / (60*60*1000); 601 if (DEBUG && pingPower != 0) { 602 Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower)); 603 } 604 btPower += pingPower; 605 BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs, 606 btPower + mBluetoothPower); 607 aggregateSippers(bs, mBluetoothSippers, "Bluetooth"); 608 } 609 610 private void addUserUsage() { 611 for (int i=0; i<mUserSippers.size(); i++) { 612 final int userId = mUserSippers.keyAt(i); 613 final List<BatterySipper> sippers = mUserSippers.valueAt(i); 614 Double userPower = mUserPower.get(userId); 615 double power = (userPower != null) ? userPower : 0.0; 616 BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power); 617 bs.userId = userId; 618 aggregateSippers(bs, sippers, "User"); 619 } 620 } 621 622 /** 623 * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio. 624 */ 625 private double getMobilePowerPerPacket() { 626 final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system 627 final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) 628 / 3600; 629 630 final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType); 631 final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType); 632 final long mobileData = mobileRx + mobileTx; 633 634 final long radioDataUptimeMs = mStats.getRadioDataUptime() / 1000; 635 final double mobilePps = radioDataUptimeMs != 0 636 ? mobileData / (double)radioDataUptimeMs 637 : (((double)MOBILE_BPS) / 8 / 2048); 638 639 return (MOBILE_POWER / mobilePps) / (60*60); 640 } 641 642 /** 643 * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio. 644 */ 645 private double getWifiPowerPerPacket() { 646 final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system 647 final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) 648 / 3600; 649 return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60); 650 } 651 652 private void processMiscUsage() { 653 addUserUsage(); 654 addPhoneUsage(); 655 addScreenUsage(); 656 addWiFiUsage(); 657 addBluetoothUsage(); 658 addIdleUsage(); // Not including cellular idle power 659 // Don't compute radio usage if it's a wifi-only device 660 ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( 661 Context.CONNECTIVITY_SERVICE); 662 if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { 663 addRadioUsage(); 664 } 665 } 666 667 private BatterySipper addEntry(DrainType drainType, long time, double power) { 668 mTotalPower += power; 669 return addEntryNoTotal(drainType, time, power); 670 } 671 672 private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) { 673 if (power > mMaxPower) mMaxPower = power; 674 BatterySipper bs = new BatterySipper(drainType, null, new double[] {power}); 675 bs.usageTime = time; 676 mUsageList.add(bs); 677 return bs; 678 } 679 680 public List<BatterySipper> getUsageList() { 681 return mUsageList; 682 } 683 684 public long getStatsPeriod() { return mStatsPeriod; } 685 686 public int getStatsType() { return mStatsType; }; 687 688 public double getMaxPower() { return mMaxPower; } 689 690 public double getTotalPower() { return mTotalPower; } 691 692 public double getMinDrainedPower() { 693 return mMinDrainedPower; 694 } 695 696 public double getMaxDrainedPower() { 697 return mMaxDrainedPower; 698 } 699 700 private void load() { 701 if (mBatteryInfo == null) { 702 return; 703 } 704 try { 705 byte[] data = mBatteryInfo.getStatistics(); 706 Parcel parcel = Parcel.obtain(); 707 parcel.unmarshall(data, 0, data.length); 708 parcel.setDataPosition(0); 709 BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR 710 .createFromParcel(parcel); 711 stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED); 712 mStats = stats; 713 } catch (RemoteException e) { 714 Log.e(TAG, "RemoteException:", e); 715 } 716 } 717} 718