Friday, April 5, 2013

Android Implementation: Settings > Battery

Fig 1. Settings > Battery (Android Jelly Bean)
This blog is trying to describe how Android battery statistics works by understanding Android source code (4.2.2) and ends with my opinions about current implementation.

1. Power Usage Display

The Java code related to Android Settings is located at src/android-version/packages/apps/Settings/. The Android Settings is a System App and the Battery part is located at Settings/src/com/android/settings/fuelgauge directory. Fig 1's activity is implemented in PowerUsageSummary.java. It consists of three parts: from top to bottom, there are BatteryStatus (14% - Discharging), BatteryHistoryChart (10h 22m 44s on battery), and BatterySipperList (Cell standby ...). If you click on the chart, it shows History details implemented in BatteryHistoryDetail.java; and if you click the rest item, e.g., Screen, it shows Use details implemented in PowerUsageDetails.java. In sum, these are display components used in Battery Settings, but where are the data/results/statistics coming from? I will describe it in the next section.

2. Power Usage Statistics

In PowerUsageSummary.java, you can find the battery statistics information source: IBatteryStats mBatteryInfo, which is the interface to access the raw battery statistics from a system service, named as "batteryinfo". What's this? This is a system service -- BatteryStatsService -- running all the time to collect power related information (as a component in ActivityManagerService). The raw information collected store at /data/system/batterystats.bin (you can see it with root privilege). 

How does BatteryStatsService collect battery related information? 
BatteryStatsService has a core component -- final BatteryStatsImpl mStats. mStats provides several methods so other system services can report the events related to battery statistics. These methods are named as noteEvent(), e.g., noteScreenOn(), noteWifiOn(), etc. The implementation of mStats can be found at com.android.internal.os.BatteryStatsImpl . mStats maintains several stopWatchTimer to record the durations of related events, e.g., the duration when the screen is on at certain brightness. Taking another example, in com.android.server.TelephoneyRegistry, it note phone state changes, signal strength changes, phone on and off to the BatteryStatsService.

How does BatteryStatsService provide battery statistics?
To access the battery statistic, you need to use getStatistics() method. For example, In PowerUsageSummary.java, you can see that everytime when the battery summary refreshes, its method refreshStats() loads fresh battery statistics into mStats by ues getStatistics() method. It writes battery statistics into a Parcel object and deliver to the caller as a byte array. The core method in getStatistics() is writeToPacel(). Except for recording timers and other indices into the parcel, it also writes mobile and total TCP bytes received/sent into it but does not contain any UDP information by using methods, e.g, getMobileTcpBytesReceived(), etc. These methods use getNetworkStatsSummary() to get the network summary from NetworkStatsFactory, which read the network statistics from three files, /proc/net/xt_qtaguid/iface_stat_all, /proc/net/xt_qtaguid/iface_stat_fmt, and /proc/net/xt_qtaguid/stats. These files are updated by native cpp methods.

3. Power Usage Computation

Now after get all statistics, the rest work is to calculate the power consumption for different users/apps. The methods are located at PowerUsageSummary.java, the algorithms for each user are listed below:
  • Screen = Sum of screenBinPower * brightnessTime
  • Idle = cpuIdlePower * (totalTime - screenOnTime)
  • Radio (Cell Standby) = Sum of strengthTime * strengthPower + scanningTime * scanningPower
  • Phone (call) = phoneOnTime * phoneOnPower
  • Bluetooth = btOnTime * btOnPower + btPingCount * btAtCmdPower
  • WiFi = (globalWifiRunningTime - AppWifiRunningTime) * wifiOnPower
  • App = cpu + wakelock + tcp + wifi + sensor
    • cpu = ratio * tmpCpuTime * powerCpuNormal[step]
    • wakelock = wakelockTime * wakelockPower
    • tcp = (tcpBytesReceived+tcpBytesSent) * averageCostPerByte
      • averageCostPerByte = (mobileCostPerByte * mobileData + wifiCostPerByte * wifiData) / (mobileData + wifiData)
      • mobileCostPerByte = MOBILE_POWER / (mobileBps / 8)
      • wifiCostPerByte = WIFI_POWER / (WIFI_BPS / 8)
    • wifi = appWifiRunningTime * wifiRunningPower + appWifiScanTime * wifiScanPower
    • sensor = sensorTime * sensorPower
  • Note that two confusing app -- Android System and Android OS. Android OS shows the power consumption from the process with PID = 0, which shows the power consumption from the kernel; while Android System shows the power consumption from a running process called Android System, which shows the power consumed by Android system services. Android also add the power consumption of wakeTime = totalWakeTime - appWakelockTime - screenOnTime (only if wakeTime > 0), i.e., the cost when the cpu is awake but the screen is off and no app is running the cpu.
Note that all power information are from com.android.internal.os.PowerProfile mPowerProfile. It loads the profile file from the source code /frameworks/base/core/res/res/xml/power_profile.xml. While the profile is quite simple if you look at the file. Different phones use different profiles but the accuracy is hard to say. For example, I found the Galaxy S3 and S3 mini use the same profile but these two devices are quite different, e.g., screen size and cpu is different.

4. Summary

Android uses a simple model to account power usage. I think it has several drawbacks:
  • It uses a simple and average power profile which may generate large error. 
  • It only counts TCP in the apps' power usage and in simple case, the tcp power usage model is linear with the amount of data transmitted, which is not realistic.
P.S. welcome to discuss with me about this topic and let me know if anything is wrong.