blob: 58f93364f6867750742bc4a712b997eaf9174486 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.database.Cursor;
import android.database.CursorWindow;
import android.util.Range;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.PowerCalculator;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Contains a snapshot of battery attribution data, on a per-subsystem and per-UID basis.
* <p>
* The totals for the entire device are returned as AggregateBatteryConsumers, which can be
* obtained by calling {@link #getAggregateBatteryConsumer(int)}.
* <p>
* Power attributed to individual apps is returned as UidBatteryConsumers, see
* {@link #getUidBatteryConsumers()}.
*
* @hide
*/
public final class BatteryUsageStats implements Parcelable, Closeable {
/**
* Scope of battery stats included in a BatteryConsumer: the entire device, just
* the apps, etc.
*
* @hide
*/
@IntDef(prefix = {"AGGREGATE_BATTERY_CONSUMER_SCOPE_"}, value = {
AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface AggregateBatteryConsumerScope {
}
/**
* Power consumption by the entire device, since last charge. The power usage in this
* scope includes both the power attributed to apps and the power unattributed to any
* apps.
*/
public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE = 0;
/**
* Aggregated power consumed by all applications, combined, since last charge. This is
* the sum of power reported in UidBatteryConsumers.
*/
public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS = 1;
public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT = 2;
// XML tags and attributes for BatteryUsageStats persistence
static final String XML_TAG_BATTERY_USAGE_STATS = "battery_usage_stats";
static final String XML_TAG_AGGREGATE = "aggregate";
static final String XML_TAG_UID = "uid";
static final String XML_TAG_USER = "user";
static final String XML_TAG_POWER_COMPONENTS = "power_components";
static final String XML_TAG_COMPONENT = "component";
static final String XML_TAG_CUSTOM_COMPONENT = "custom_component";
static final String XML_ATTR_ID = "id";
static final String XML_ATTR_UID = "uid";
static final String XML_ATTR_USER_ID = "user_id";
static final String XML_ATTR_SCOPE = "scope";
static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_";
static final String XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA = "includes_proc_state_data";
static final String XML_ATTR_START_TIMESTAMP = "start_timestamp";
static final String XML_ATTR_END_TIMESTAMP = "end_timestamp";
static final String XML_ATTR_PROCESS_STATE = "process_state";
static final String XML_ATTR_POWER = "power";
static final String XML_ATTR_DURATION = "duration";
static final String XML_ATTR_MODEL = "model";
static final String XML_ATTR_BATTERY_CAPACITY = "battery_capacity";
static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct";
static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower";
static final String XML_ATTR_DISCHARGE_UPPER = "discharge_upper";
static final String XML_ATTR_DISCHARGE_DURATION = "discharge_duration";
static final String XML_ATTR_BATTERY_REMAINING = "battery_remaining";
static final String XML_ATTR_CHARGE_REMAINING = "charge_remaining";
static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package";
static final String XML_ATTR_TIME_IN_FOREGROUND = "time_in_foreground";
static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background";
// We need about 700 bytes per UID
private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 5_000 * 700;
private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
private final int mDischargePercentage;
private final double mBatteryCapacityMah;
private final long mStatsStartTimestampMs;
private final long mStatsEndTimestampMs;
private final long mStatsDurationMs;
private final double mDischargedPowerLowerBound;
private final double mDischargedPowerUpperBound;
private final long mDischargeDurationMs;
private final long mBatteryTimeRemainingMs;
private final long mChargeTimeRemainingMs;
private final String[] mCustomPowerComponentNames;
private final boolean mIncludesPowerModels;
private final boolean mIncludesProcessStateData;
private final List<UidBatteryConsumer> mUidBatteryConsumers;
private final List<UserBatteryConsumer> mUserBatteryConsumers;
private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
private final BatteryStatsHistory mBatteryStatsHistory;
private CursorWindow mBatteryConsumersCursorWindow;
private BatteryUsageStats(@NonNull Builder builder) {
mStatsStartTimestampMs = builder.mStatsStartTimestampMs;
mStatsEndTimestampMs = builder.mStatsEndTimestampMs;
mStatsDurationMs = builder.getStatsDuration();
mBatteryCapacityMah = builder.mBatteryCapacityMah;
mDischargePercentage = builder.mDischargePercentage;
mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah;
mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah;
mDischargeDurationMs = builder.mDischargeDurationMs;
mBatteryStatsHistory = builder.mBatteryStatsHistory;
mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
mIncludesPowerModels = builder.mIncludePowerModels;
mIncludesProcessStateData = builder.mIncludesProcessStateData;
mBatteryConsumersCursorWindow = builder.mBatteryConsumersCursorWindow;
double totalPowerMah = 0;
final int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size();
mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount);
for (int i = 0; i < uidBatteryConsumerCount; i++) {
final UidBatteryConsumer.Builder uidBatteryConsumerBuilder =
builder.mUidBatteryConsumerBuilders.valueAt(i);
if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) {
final UidBatteryConsumer consumer = uidBatteryConsumerBuilder.build();
totalPowerMah += consumer.getConsumedPower();
mUidBatteryConsumers.add(consumer);
}
}
final int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size();
mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount);
for (int i = 0; i < userBatteryConsumerCount; i++) {
final UserBatteryConsumer consumer =
builder.mUserBatteryConsumerBuilders.valueAt(i).build();
totalPowerMah += consumer.getConsumedPower();
mUserBatteryConsumers.add(consumer);
}
builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setConsumedPower(totalPowerMah);
mAggregateBatteryConsumers =
new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
mAggregateBatteryConsumers[i] = builder.mAggregateBatteryConsumersBuilders[i].build();
}
}
/**
* Timestamp (as returned by System.currentTimeMillis()) of the latest battery stats reset, in
* milliseconds.
*/
public long getStatsStartTimestamp() {
return mStatsStartTimestampMs;
}
/**
* Timestamp (as returned by System.currentTimeMillis()) of when the stats snapshot was taken,
* in milliseconds.
*/
public long getStatsEndTimestamp() {
return mStatsEndTimestampMs;
}
/**
* Returns the duration of the stats session captured by this BatteryUsageStats.
* In rare cases, statsDuration != statsEndTimestamp - statsStartTimestamp. This may
* happen when BatteryUsageStats represents an accumulation of data across multiple
* non-contiguous sessions.
*/
public long getStatsDuration() {
return mStatsDurationMs;
}
/**
* Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully
* charged), in mAh
*/
public double getConsumedPower() {
return mAggregateBatteryConsumers[AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE]
.getConsumedPower();
}
/**
* Returns battery capacity in milli-amp-hours.
*/
public double getBatteryCapacity() {
return mBatteryCapacityMah;
}
/**
* Portion of battery charge drained since BatteryStats reset (e.g. due to being fully
* charged), as percentage of the full charge in the range [0:100]. May exceed 100 if
* the device repeatedly charged and discharged prior to the reset.
*/
public int getDischargePercentage() {
return mDischargePercentage;
}
/**
* Returns the discharged power since BatteryStats were last reset, in mAh as an estimated
* range.
*/
public Range<Double> getDischargedPowerRange() {
return Range.create(mDischargedPowerLowerBound, mDischargedPowerUpperBound);
}
/**
* Returns the total amount of time the battery was discharging.
*/
public long getDischargeDurationMs() {
return mDischargeDurationMs;
}
/**
* Returns an approximation for how much run time (in milliseconds) is remaining on
* the battery. Returns -1 if no time can be computed: either there is not
* enough current data to make a decision, or the battery is currently
* charging.
*/
public long getBatteryTimeRemainingMs() {
return mBatteryTimeRemainingMs;
}
/**
* Returns an approximation for how much time (in milliseconds) remains until the battery
* is fully charged. Returns -1 if no time can be computed: either there is not
* enough current data to make a decision, or the battery is currently discharging.
*/
public long getChargeTimeRemainingMs() {
return mChargeTimeRemainingMs;
}
/**
* Returns a battery consumer for the specified battery consumer type.
*/
public BatteryConsumer getAggregateBatteryConsumer(
@AggregateBatteryConsumerScope int scope) {
return mAggregateBatteryConsumers[scope];
}
@NonNull
public List<UidBatteryConsumer> getUidBatteryConsumers() {
return mUidBatteryConsumers;
}
@NonNull
public List<UserBatteryConsumer> getUserBatteryConsumers() {
return mUserBatteryConsumers;
}
/**
* Returns the names of custom power components in order, so the first name in the array
* corresponds to the custom componentId
* {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}.
*/
@NonNull
public String[] getCustomPowerComponentNames() {
return mCustomPowerComponentNames;
}
public boolean isProcessStateDataIncluded() {
return mIncludesProcessStateData;
}
/**
* Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s.
*/
@NonNull
public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
if (mBatteryStatsHistory == null) {
throw new IllegalStateException(
"Battery history was not requested in the BatteryUsageStatsQuery");
}
return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
}
@Override
public int describeContents() {
return 0;
}
private BatteryUsageStats(@NonNull Parcel source) {
mStatsStartTimestampMs = source.readLong();
mStatsEndTimestampMs = source.readLong();
mStatsDurationMs = source.readLong();
mBatteryCapacityMah = source.readDouble();
mDischargePercentage = source.readInt();
mDischargedPowerLowerBound = source.readDouble();
mDischargedPowerUpperBound = source.readDouble();
mDischargeDurationMs = source.readLong();
mBatteryTimeRemainingMs = source.readLong();
mChargeTimeRemainingMs = source.readLong();
mCustomPowerComponentNames = source.readStringArray();
mIncludesPowerModels = source.readBoolean();
mIncludesProcessStateData = source.readBoolean();
mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source);
BatteryConsumer.BatteryConsumerDataLayout dataLayout =
BatteryConsumer.createBatteryConsumerDataLayout(mCustomPowerComponentNames,
mIncludesPowerModels, mIncludesProcessStateData);
final int numRows = mBatteryConsumersCursorWindow.getNumRows();
mAggregateBatteryConsumers =
new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
mUidBatteryConsumers = new ArrayList<>(numRows);
mUserBatteryConsumers = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
final BatteryConsumer.BatteryConsumerData data =
new BatteryConsumer.BatteryConsumerData(mBatteryConsumersCursorWindow, i,
dataLayout);
int consumerType = mBatteryConsumersCursorWindow.getInt(i,
BatteryConsumer.COLUMN_INDEX_BATTERY_CONSUMER_TYPE);
switch (consumerType) {
case AggregateBatteryConsumer.CONSUMER_TYPE_AGGREGATE: {
final AggregateBatteryConsumer consumer = new AggregateBatteryConsumer(data);
mAggregateBatteryConsumers[consumer.getScope()] = consumer;
break;
}
case UidBatteryConsumer.CONSUMER_TYPE_UID: {
mUidBatteryConsumers.add(new UidBatteryConsumer(data));
break;
}
case UserBatteryConsumer.CONSUMER_TYPE_USER:
mUserBatteryConsumers.add(new UserBatteryConsumer(data));
break;
}
}
if (source.readBoolean()) {
mBatteryStatsHistory = BatteryStatsHistory.createFromBatteryUsageStatsParcel(source);
} else {
mBatteryStatsHistory = null;
}
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mStatsStartTimestampMs);
dest.writeLong(mStatsEndTimestampMs);
dest.writeLong(mStatsDurationMs);
dest.writeDouble(mBatteryCapacityMah);
dest.writeInt(mDischargePercentage);
dest.writeDouble(mDischargedPowerLowerBound);
dest.writeDouble(mDischargedPowerUpperBound);
dest.writeLong(mDischargeDurationMs);
dest.writeLong(mBatteryTimeRemainingMs);
dest.writeLong(mChargeTimeRemainingMs);
dest.writeStringArray(mCustomPowerComponentNames);
dest.writeBoolean(mIncludesPowerModels);
dest.writeBoolean(mIncludesProcessStateData);
mBatteryConsumersCursorWindow.writeToParcel(dest, flags);
if (mBatteryStatsHistory != null) {
dest.writeBoolean(true);
mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest);
} else {
dest.writeBoolean(false);
}
}
@NonNull
public static final Creator<BatteryUsageStats> CREATOR = new Creator<BatteryUsageStats>() {
public BatteryUsageStats createFromParcel(@NonNull Parcel source) {
return new BatteryUsageStats(source);
}
public BatteryUsageStats[] newArray(int size) {
return new BatteryUsageStats[size];
}
};
/** Returns a proto (as used for atoms.proto) corresponding to this BatteryUsageStats. */
public byte[] getStatsProto() {
// ProtoOutputStream.getRawSize() returns the buffer size before compaction.
// BatteryUsageStats contains a lot of integers, so compaction of integers to
// varint reduces the size of the proto buffer by as much as 50%.
int maxRawSize = (int) (STATSD_PULL_ATOM_MAX_BYTES * 1.75);
// Limit the number of attempts in order to prevent an infinite loop
for (int i = 0; i < 3; i++) {
final ProtoOutputStream proto = new ProtoOutputStream();
writeStatsProto(proto, maxRawSize);
final int rawSize = proto.getRawSize();
final byte[] protoOutput = proto.getBytes();
if (protoOutput.length <= STATSD_PULL_ATOM_MAX_BYTES) {
return protoOutput;
}
// Adjust maxRawSize proportionately and try again.
maxRawSize =
(int) ((long) STATSD_PULL_ATOM_MAX_BYTES * rawSize / protoOutput.length - 1024);
}
// Fallback: if we have failed to generate a proto smaller than STATSD_PULL_ATOM_MAX_BYTES,
// just generate a proto with the _rawSize_ of STATSD_PULL_ATOM_MAX_BYTES, which is
// guaranteed to produce a compacted proto (significantly) smaller than
// STATSD_PULL_ATOM_MAX_BYTES.
final ProtoOutputStream proto = new ProtoOutputStream();
writeStatsProto(proto, STATSD_PULL_ATOM_MAX_BYTES);
return proto.getBytes();
}
@NonNull
private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) {
final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp());
proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, getStatsEndTimestamp());
proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration());
proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
getDischargePercentage());
proto.write(BatteryUsageStatsAtomsProto.DISCHARGE_DURATION_MILLIS,
getDischargeDurationMs());
deviceBatteryConsumer.writeStatsProto(proto,
BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
writeUidBatteryConsumersProto(proto, maxRawSize);
}
/**
* Writes the UidBatteryConsumers data, held by this BatteryUsageStats, to the proto (as used
* for atoms.proto).
*/
private void writeUidBatteryConsumersProto(ProtoOutputStream proto, int maxRawSize) {
final List<UidBatteryConsumer> consumers = getUidBatteryConsumers();
// Order consumers by descending weight (a combination of consumed power and usage time)
consumers.sort(Comparator.comparingDouble(this::getUidBatteryConsumerWeight).reversed());
final int size = consumers.size();
for (int i = 0; i < size; i++) {
final UidBatteryConsumer consumer = consumers.get(i);
final long fgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
final long bgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
final boolean hasBaseData = consumer.hasStatsProtoData();
if (fgMs == 0 && bgMs == 0 && !hasBaseData) {
continue;
}
final long token = proto.start(BatteryUsageStatsAtomsProto.UID_BATTERY_CONSUMERS);
proto.write(
BatteryUsageStatsAtomsProto.UidBatteryConsumer.UID,
consumer.getUid());
if (hasBaseData) {
consumer.writeStatsProto(proto,
BatteryUsageStatsAtomsProto.UidBatteryConsumer.BATTERY_CONSUMER_DATA);
}
proto.write(
BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_FOREGROUND_MILLIS,
fgMs);
proto.write(
BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS,
bgMs);
proto.end(token);
if (proto.getRawSize() >= maxRawSize) {
break;
}
}
}
private static final double WEIGHT_CONSUMED_POWER = 1;
// Weight one hour in foreground the same as 100 mAh of power drain
private static final double WEIGHT_FOREGROUND_STATE = 100.0 / (1 * 60 * 60 * 1000);
// Weight one hour in background the same as 300 mAh of power drain
private static final double WEIGHT_BACKGROUND_STATE = 300.0 / (1 * 60 * 60 * 1000);
/**
* Computes the weight associated with a UidBatteryConsumer, which is used for sorting.
* We want applications with the largest consumed power as well as applications
* with the highest usage time to be included in the statsd atom.
*/
private double getUidBatteryConsumerWeight(UidBatteryConsumer uidBatteryConsumer) {
final double consumedPower = uidBatteryConsumer.getConsumedPower();
final long timeInForeground =
uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
final long timeInBackground =
uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
return consumedPower * WEIGHT_CONSUMED_POWER
+ timeInForeground * WEIGHT_FOREGROUND_STATE
+ timeInBackground * WEIGHT_BACKGROUND_STATE;
}
/**
* Prints the stats in a human-readable format.
*/
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println(" Estimated power use (mAh):");
pw.print(prefix);
pw.print(" Capacity: ");
PowerCalculator.printPowerMah(pw, getBatteryCapacity());
pw.print(", Computed drain: ");
PowerCalculator.printPowerMah(pw, getConsumedPower());
final Range<Double> dischargedPowerRange = getDischargedPowerRange();
pw.print(", actual drain: ");
PowerCalculator.printPowerMah(pw, dischargedPowerRange.getLower());
if (!dischargedPowerRange.getLower().equals(dischargedPowerRange.getUpper())) {
pw.print("-");
PowerCalculator.printPowerMah(pw, dischargedPowerRange.getUpper());
}
pw.println();
pw.println(" Global");
final BatteryConsumer deviceConsumer = getAggregateBatteryConsumer(
AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
final BatteryConsumer appsConsumer = getAggregateBatteryConsumer(
AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
for (BatteryConsumer.Key key : deviceConsumer.getKeys(componentId)) {
final double devicePowerMah = deviceConsumer.getConsumedPower(key);
final double appsPowerMah = appsConsumer.getConsumedPower(key);
if (devicePowerMah == 0 && appsPowerMah == 0) {
continue;
}
String label = BatteryConsumer.powerComponentIdToString(componentId);
if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
label = label
+ "(" + BatteryConsumer.processStateToString(key.processState) + ")";
}
printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah,
deviceConsumer.getPowerModel(key),
deviceConsumer.getUsageDurationMillis(key));
}
}
for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ mCustomPowerComponentNames.length;
componentId++) {
final double devicePowerMah =
deviceConsumer.getConsumedPowerForCustomComponent(componentId);
final double appsPowerMah =
appsConsumer.getConsumedPowerForCustomComponent(componentId);
if (devicePowerMah == 0 && appsPowerMah == 0) {
continue;
}
printPowerComponent(pw, prefix, deviceConsumer.getCustomPowerComponentName(componentId),
devicePowerMah, appsPowerMah,
BatteryConsumer.POWER_MODEL_UNDEFINED,
deviceConsumer.getUsageDurationForCustomComponentMillis(componentId));
}
dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers());
dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers());
pw.println();
}
private void printPowerComponent(PrintWriter pw, String prefix, String label,
double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
StringBuilder sb = new StringBuilder();
sb.append(prefix).append(" ").append(label).append(": ")
.append(BatteryStats.formatCharge(devicePowerMah));
if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
&& powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
sb.append(" [");
sb.append(BatteryConsumer.powerModelToString(powerModel));
sb.append("]");
}
sb.append(" apps: ").append(BatteryStats.formatCharge(appsPowerMah));
if (durationMs != 0) {
sb.append(" duration: ");
BatteryStats.formatTimeMs(sb, durationMs);
}
pw.println(sb.toString());
}
private void dumpSortedBatteryConsumers(PrintWriter pw, String prefix,
List<? extends BatteryConsumer> batteryConsumers) {
batteryConsumers.sort(
Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
.reversed());
for (BatteryConsumer consumer : batteryConsumers) {
if (consumer.getConsumedPower() == 0) {
continue;
}
pw.print(prefix);
pw.print(" ");
consumer.dump(pw);
pw.println();
}
}
/** Serializes this object to XML */
public void writeXml(TypedXmlSerializer serializer) throws IOException {
serializer.startTag(null, XML_TAG_BATTERY_USAGE_STATS);
for (int i = 0; i < mCustomPowerComponentNames.length; i++) {
serializer.attribute(null, XML_ATTR_PREFIX_CUSTOM_COMPONENT + i,
mCustomPowerComponentNames[i]);
}
serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA,
mIncludesProcessStateData);
serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs);
serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs);
serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs);
serializer.attributeDouble(null, XML_ATTR_BATTERY_CAPACITY, mBatteryCapacityMah);
serializer.attributeInt(null, XML_ATTR_DISCHARGE_PERCENT, mDischargePercentage);
serializer.attributeDouble(null, XML_ATTR_DISCHARGE_LOWER, mDischargedPowerLowerBound);
serializer.attributeDouble(null, XML_ATTR_DISCHARGE_UPPER, mDischargedPowerUpperBound);
serializer.attributeLong(null, XML_ATTR_DISCHARGE_DURATION, mDischargeDurationMs);
serializer.attributeLong(null, XML_ATTR_BATTERY_REMAINING, mBatteryTimeRemainingMs);
serializer.attributeLong(null, XML_ATTR_CHARGE_REMAINING, mChargeTimeRemainingMs);
for (int scope = 0; scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
scope++) {
mAggregateBatteryConsumers[scope].writeToXml(serializer, scope);
}
for (UidBatteryConsumer consumer : mUidBatteryConsumers) {
consumer.writeToXml(serializer);
}
for (UserBatteryConsumer consumer : mUserBatteryConsumers) {
consumer.writeToXml(serializer);
}
serializer.endTag(null, XML_TAG_BATTERY_USAGE_STATS);
}
/** Parses an XML representation of BatteryUsageStats */
public static BatteryUsageStats createFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
Builder builder = null;
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG
&& parser.getName().equals(XML_TAG_BATTERY_USAGE_STATS)) {
List<String> customComponentNames = new ArrayList<>();
int i = 0;
while (true) {
int index = parser.getAttributeIndex(null,
XML_ATTR_PREFIX_CUSTOM_COMPONENT + i);
if (index == -1) {
break;
}
customComponentNames.add(parser.getAttributeValue(index));
i++;
}
final boolean includesProcStateData = parser.getAttributeBoolean(null,
XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false);
builder = new Builder(customComponentNames.toArray(new String[0]), true,
includesProcStateData);
builder.setStatsStartTimestamp(
parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP));
builder.setStatsEndTimestamp(
parser.getAttributeLong(null, XML_ATTR_END_TIMESTAMP));
builder.setStatsDuration(
parser.getAttributeLong(null, XML_ATTR_DURATION));
builder.setBatteryCapacity(
parser.getAttributeDouble(null, XML_ATTR_BATTERY_CAPACITY));
builder.setDischargePercentage(
parser.getAttributeInt(null, XML_ATTR_DISCHARGE_PERCENT));
builder.setDischargedPowerRange(
parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_LOWER),
parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_UPPER));
builder.setDischargeDurationMs(
parser.getAttributeLong(null, XML_ATTR_DISCHARGE_DURATION));
builder.setBatteryTimeRemainingMs(
parser.getAttributeLong(null, XML_ATTR_BATTERY_REMAINING));
builder.setChargeTimeRemainingMs(
parser.getAttributeLong(null, XML_ATTR_CHARGE_REMAINING));
eventType = parser.next();
break;
}
eventType = parser.next();
}
if (builder == null) {
throw new XmlPullParserException("No root element");
}
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
switch (parser.getName()) {
case XML_TAG_AGGREGATE:
AggregateBatteryConsumer.parseXml(parser, builder);
break;
case XML_TAG_UID:
UidBatteryConsumer.createFromXml(parser, builder);
break;
case XML_TAG_USER:
UserBatteryConsumer.createFromXml(parser, builder);
break;
}
}
eventType = parser.next();
}
return builder.build();
}
@Override
public void close() throws IOException {
mBatteryConsumersCursorWindow.close();
mBatteryConsumersCursorWindow = null;
}
@Override
protected void finalize() throws Throwable {
if (mBatteryConsumersCursorWindow != null) {
mBatteryConsumersCursorWindow.close();
}
super.finalize();
}
/**
* Builder for BatteryUsageStats.
*/
public static final class Builder {
private final CursorWindow mBatteryConsumersCursorWindow;
@NonNull
private final String[] mCustomPowerComponentNames;
private final boolean mIncludePowerModels;
private final boolean mIncludesProcessStateData;
private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout;
private long mStatsStartTimestampMs;
private long mStatsEndTimestampMs;
private long mStatsDurationMs = -1;
private double mBatteryCapacityMah;
private int mDischargePercentage;
private double mDischargedPowerLowerBoundMah;
private double mDischargedPowerUpperBoundMah;
private long mDischargeDurationMs;
private long mBatteryTimeRemainingMs = -1;
private long mChargeTimeRemainingMs = -1;
private final AggregateBatteryConsumer.Builder[] mAggregateBatteryConsumersBuilders =
new AggregateBatteryConsumer.Builder[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders =
new SparseArray<>();
private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
new SparseArray<>();
private BatteryStatsHistory mBatteryStatsHistory;
public Builder(@NonNull String[] customPowerComponentNames) {
this(customPowerComponentNames, false, false);
}
public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
boolean includeProcessStateData) {
mBatteryConsumersCursorWindow =
new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
mBatteryConsumerDataLayout =
BatteryConsumer.createBatteryConsumerDataLayout(customPowerComponentNames,
includePowerModels, includeProcessStateData);
mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount);
mCustomPowerComponentNames = customPowerComponentNames;
mIncludePowerModels = includePowerModels;
mIncludesProcessStateData = includeProcessStateData;
for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
mAggregateBatteryConsumersBuilders[scope] =
new AggregateBatteryConsumer.Builder(data, scope);
}
}
public boolean isProcessStateDataNeeded() {
return mIncludesProcessStateData;
}
/**
* Constructs a read-only object using the Builder values.
*/
@NonNull
public BatteryUsageStats build() {
return new BatteryUsageStats(this);
}
/**
* Sets the battery capacity in milli-amp-hours.
*/
public Builder setBatteryCapacity(double batteryCapacityMah) {
mBatteryCapacityMah = batteryCapacityMah;
return this;
}
/**
* Sets the timestamp of the latest battery stats reset, in milliseconds.
*/
public Builder setStatsStartTimestamp(long statsStartTimestampMs) {
mStatsStartTimestampMs = statsStartTimestampMs;
return this;
}
/**
* Sets the timestamp of when the battery stats snapshot was taken, in milliseconds.
*/
public Builder setStatsEndTimestamp(long statsEndTimestampMs) {
mStatsEndTimestampMs = statsEndTimestampMs;
return this;
}
/**
* Sets the duration of the stats session. The default value of this field is
* statsEndTimestamp - statsStartTimestamp.
*/
public Builder setStatsDuration(long statsDurationMs) {
mStatsDurationMs = statsDurationMs;
return this;
}
private long getStatsDuration() {
if (mStatsDurationMs != -1) {
return mStatsDurationMs;
} else {
return mStatsEndTimestampMs - mStatsStartTimestampMs;
}
}
/**
* Sets the battery discharge amount since BatteryStats reset as percentage of the full
* charge.
*/
@NonNull
public Builder setDischargePercentage(int dischargePercentage) {
mDischargePercentage = dischargePercentage;
return this;
}
/**
* Sets the estimated battery discharge range.
*/
@NonNull
public Builder setDischargedPowerRange(double dischargedPowerLowerBoundMah,
double dischargedPowerUpperBoundMah) {
mDischargedPowerLowerBoundMah = dischargedPowerLowerBoundMah;
mDischargedPowerUpperBoundMah = dischargedPowerUpperBoundMah;
return this;
}
/**
* Sets the total battery discharge time, in milliseconds.
*/
@NonNull
public Builder setDischargeDurationMs(long durationMs) {
mDischargeDurationMs = durationMs;
return this;
}
/**
* Sets an approximation for how much time (in milliseconds) remains until the battery
* is fully discharged.
*/
@NonNull
public Builder setBatteryTimeRemainingMs(long batteryTimeRemainingMs) {
mBatteryTimeRemainingMs = batteryTimeRemainingMs;
return this;
}
/**
* Sets an approximation for how much time (in milliseconds) remains until the battery
* is fully charged.
*/
@NonNull
public Builder setChargeTimeRemainingMs(long chargeTimeRemainingMs) {
mChargeTimeRemainingMs = chargeTimeRemainingMs;
return this;
}
/**
* Sets the parceled recent history.
*/
@NonNull
public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory) {
mBatteryStatsHistory = batteryStatsHistory;
return this;
}
/**
* Creates or returns an AggregateBatteryConsumer builder, which represents aggregate
* battery consumption data for the specified scope.
*/
@NonNull
public AggregateBatteryConsumer.Builder getAggregateBatteryConsumerBuilder(
@AggregateBatteryConsumerScope int scope) {
return mAggregateBatteryConsumersBuilders[scope];
}
/**
* Creates or returns a UidBatteryConsumer, which represents battery attribution
* data for an individual UID.
*/
@NonNull
public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder(
@NonNull BatteryStats.Uid batteryStatsUid) {
int uid = batteryStatsUid.getUid();
UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid);
if (builder == null) {
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
builder = new UidBatteryConsumer.Builder(data, batteryStatsUid);
mUidBatteryConsumerBuilders.put(uid, builder);
}
return builder;
}
/**
* Creates or returns a UidBatteryConsumer, which represents battery attribution
* data for an individual UID. This version of the method is not suitable for use
* with PowerCalculators.
*/
@NonNull
public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder(int uid) {
UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid);
if (builder == null) {
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
builder = new UidBatteryConsumer.Builder(data, uid);
mUidBatteryConsumerBuilders.put(uid, builder);
}
return builder;
}
/**
* Creates or returns a UserBatteryConsumer, which represents battery attribution
* data for an individual {@link UserHandle}.
*/
@NonNull
public UserBatteryConsumer.Builder getOrCreateUserBatteryConsumerBuilder(int userId) {
UserBatteryConsumer.Builder builder = mUserBatteryConsumerBuilders.get(userId);
if (builder == null) {
final BatteryConsumer.BatteryConsumerData data =
BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow,
mBatteryConsumerDataLayout);
builder = new UserBatteryConsumer.Builder(data, userId);
mUserBatteryConsumerBuilders.put(userId, builder);
}
return builder;
}
@NonNull
public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() {
return mUidBatteryConsumerBuilders;
}
/**
* Adds battery usage stats from another snapshots. The two snapshots are assumed to be
* non-overlapping, meaning that the power consumption estimates and session durations
* can be simply summed across the two snapshots. This remains true even if the timestamps
* seem to indicate that the sessions are in fact overlapping: timestamps may be off as a
* result of realtime clock adjustments by the user or the system.
*/
@NonNull
public Builder add(BatteryUsageStats stats) {
if (!Arrays.equals(mCustomPowerComponentNames, stats.mCustomPowerComponentNames)) {
throw new IllegalArgumentException(
"BatteryUsageStats have different custom power components");
}
if (mIncludesProcessStateData && !stats.mIncludesProcessStateData) {
throw new IllegalArgumentException(
"Added BatteryUsageStats does not include process state data");
}
if (mUserBatteryConsumerBuilders.size() != 0
|| !stats.getUserBatteryConsumers().isEmpty()) {
throw new UnsupportedOperationException(
"Combining UserBatteryConsumers is not supported");
}
mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound;
mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound;
mDischargePercentage += stats.mDischargePercentage;
mDischargeDurationMs += stats.mDischargeDurationMs;
mStatsDurationMs = getStatsDuration() + stats.getStatsDuration();
if (mStatsStartTimestampMs == 0
|| stats.mStatsStartTimestampMs < mStatsStartTimestampMs) {
mStatsStartTimestampMs = stats.mStatsStartTimestampMs;
}
final boolean addingLaterSnapshot = stats.mStatsEndTimestampMs > mStatsEndTimestampMs;
if (addingLaterSnapshot) {
mStatsEndTimestampMs = stats.mStatsEndTimestampMs;
}
for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) {
getAggregateBatteryConsumerBuilder(scope)
.add(stats.mAggregateBatteryConsumers[scope]);
}
for (UidBatteryConsumer consumer : stats.getUidBatteryConsumers()) {
getOrCreateUidBatteryConsumerBuilder(consumer.getUid()).add(consumer);
}
if (addingLaterSnapshot) {
mBatteryCapacityMah = stats.mBatteryCapacityMah;
mBatteryTimeRemainingMs = stats.mBatteryTimeRemainingMs;
mChargeTimeRemainingMs = stats.mChargeTimeRemainingMs;
}
return this;
}
/**
* Dumps raw contents of the cursor window for debugging.
*/
void dump(PrintWriter writer) {
final int numRows = mBatteryConsumersCursorWindow.getNumRows();
int numColumns = mBatteryConsumerDataLayout.columnCount;
for (int i = 0; i < numRows; i++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < numColumns; j++) {
final int type = mBatteryConsumersCursorWindow.getType(i, j);
switch (type) {
case Cursor.FIELD_TYPE_NULL:
sb.append("null, ");
break;
case Cursor.FIELD_TYPE_INTEGER:
sb.append(mBatteryConsumersCursorWindow.getInt(i, j)).append(", ");
break;
case Cursor.FIELD_TYPE_FLOAT:
sb.append(mBatteryConsumersCursorWindow.getFloat(i, j)).append(", ");
break;
case Cursor.FIELD_TYPE_STRING:
sb.append(mBatteryConsumersCursorWindow.getString(i, j)).append(", ");
break;
case Cursor.FIELD_TYPE_BLOB:
sb.append("BLOB, ");
break;
}
}
sb.setLength(sb.length() - 2);
writer.println(sb);
}
}
}
}