blob: 0f94cbef3886ed2bf97d00e9418c0d0b0882887d [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.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.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 {
/**
* 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;
private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
// 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_START_TIMESTAMP = "start_timestamp";
static final String XML_ATTR_END_TIMESTAMP = "end_timestamp";
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_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";
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 mBatteryTimeRemainingMs;
private final long mChargeTimeRemainingMs;
private final String[] mCustomPowerComponentNames;
private final List<UidBatteryConsumer> mUidBatteryConsumers;
private final List<UserBatteryConsumer> mUserBatteryConsumers;
private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
private final Parcel mHistoryBuffer;
private final List<BatteryStats.HistoryTag> mHistoryTagPool;
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;
mHistoryBuffer = builder.mHistoryBuffer;
mHistoryTagPool = builder.mHistoryTagPool;
mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
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 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;
}
/**
* Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s.
*/
@NonNull
public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
if (mHistoryBuffer == null) {
throw new IllegalStateException(
"Battery history was not requested in the BatteryUsageStatsQuery");
}
return new BatteryStatsHistoryIterator(new BatteryStatsHistory(mHistoryBuffer),
mHistoryTagPool);
}
@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();
mBatteryTimeRemainingMs = source.readLong();
mChargeTimeRemainingMs = source.readLong();
mCustomPowerComponentNames = source.readStringArray();
mAggregateBatteryConsumers =
new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
mAggregateBatteryConsumers[i] =
AggregateBatteryConsumer.CREATOR.createFromParcel(source);
mAggregateBatteryConsumers[i].setCustomPowerComponentNames(mCustomPowerComponentNames);
}
// UidBatteryConsumers are included as a blob to avoid a TransactionTooLargeException
final Parcel blob = Parcel.obtain();
final byte[] bytes = source.readBlob();
blob.unmarshall(bytes, 0, bytes.length);
blob.setDataPosition(0);
final int uidCount = blob.readInt();
mUidBatteryConsumers = new ArrayList<>(uidCount);
for (int i = 0; i < uidCount; i++) {
final UidBatteryConsumer consumer =
UidBatteryConsumer.CREATOR.createFromParcel(blob);
consumer.setCustomPowerComponentNames(mCustomPowerComponentNames);
mUidBatteryConsumers.add(consumer);
}
blob.recycle();
int userCount = source.readInt();
mUserBatteryConsumers = new ArrayList<>(userCount);
for (int i = 0; i < userCount; i++) {
final UserBatteryConsumer consumer =
UserBatteryConsumer.CREATOR.createFromParcel(source);
consumer.setCustomPowerComponentNames(mCustomPowerComponentNames);
mUserBatteryConsumers.add(consumer);
}
if (source.readBoolean()) {
final byte[] historyBlob = source.readBlob();
mHistoryBuffer = Parcel.obtain();
mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length);
int historyTagCount = source.readInt();
mHistoryTagPool = new ArrayList<>(historyTagCount);
for (int i = 0; i < historyTagCount; i++) {
BatteryStats.HistoryTag tag = new BatteryStats.HistoryTag();
tag.string = source.readString();
tag.uid = source.readInt();
tag.poolIdx = source.readInt();
mHistoryTagPool.add(tag);
}
} else {
mHistoryBuffer = null;
mHistoryTagPool = 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(mBatteryTimeRemainingMs);
dest.writeLong(mChargeTimeRemainingMs);
dest.writeStringArray(mCustomPowerComponentNames);
for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
mAggregateBatteryConsumers[i].writeToParcel(dest, flags);
}
// UidBatteryConsumers are included as a blob, because each UidBatteryConsumer
// takes > 300 bytes, so a typical number of UIDs in the system, 300 would result
// in a 90 kB Parcel, which is not safe to pass in a binder transaction because
// of the possibility of TransactionTooLargeException
final Parcel blob = Parcel.obtain();
blob.writeInt(mUidBatteryConsumers.size());
for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
mUidBatteryConsumers.get(i).writeToParcel(blob, flags);
}
dest.writeBlob(blob.marshall());
blob.recycle();
dest.writeInt(mUserBatteryConsumers.size());
for (int i = mUserBatteryConsumers.size() - 1; i >= 0; i--) {
mUserBatteryConsumers.get(i).writeToParcel(dest, flags);
}
if (mHistoryBuffer != null) {
dest.writeBoolean(true);
dest.writeBlob(mHistoryBuffer.marshall());
dest.writeInt(mHistoryTagPool.size());
for (int i = mHistoryTagPool.size() - 1; i >= 0; i--) {
final BatteryStats.HistoryTag tag = mHistoryTagPool.get(i);
dest.writeString(tag.string);
dest.writeInt(tag.uid);
dest.writeInt(tag.poolIdx);
}
} 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());
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++) {
final double devicePowerMah = deviceConsumer.getConsumedPower(componentId);
final double appsPowerMah = appsConsumer.getConsumedPower(componentId);
if (devicePowerMah == 0 && appsPowerMah == 0) {
continue;
}
final String componentName = BatteryConsumer.powerComponentIdToString(componentId);
printPowerComponent(pw, prefix, componentName, devicePowerMah, appsPowerMah,
deviceConsumer.getPowerModel(componentId),
deviceConsumer.getUsageDurationMillis(componentId));
}
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());
}
private void printPowerComponent(PrintWriter pw, String prefix, String componentName,
double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
StringBuilder sb = new StringBuilder();
sb.append(prefix).append(" ").append(componentName).append(": ")
.append(PowerCalculator.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(PowerCalculator.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.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_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++;
}
builder = new Builder(
customComponentNames.toArray(new String[0]), true);
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.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();
}
/**
* Builder for BatteryUsageStats.
*/
public static final class Builder {
@NonNull
private final String[] mCustomPowerComponentNames;
private final boolean mIncludePowerModels;
private long mStatsStartTimestampMs;
private long mStatsEndTimestampMs;
private long mStatsDurationMs = -1;
private double mBatteryCapacityMah;
private int mDischargePercentage;
private double mDischargedPowerLowerBoundMah;
private double mDischargedPowerUpperBoundMah;
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 Parcel mHistoryBuffer;
private List<BatteryStats.HistoryTag> mHistoryTagPool;
public Builder(@NonNull String[] customPowerComponentNames) {
this(customPowerComponentNames, false);
}
public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels) {
mCustomPowerComponentNames = customPowerComponentNames;
mIncludePowerModels = includePowerModels;
for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
mAggregateBatteryConsumersBuilders[i] = new AggregateBatteryConsumer.Builder(
customPowerComponentNames, includePowerModels);
}
}
/**
* 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 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(Parcel historyBuffer,
List<BatteryStats.HistoryTag> historyTagPool) {
mHistoryBuffer = historyBuffer;
mHistoryTagPool = historyTagPool;
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) {
builder = new UidBatteryConsumer.Builder(mCustomPowerComponentNames,
mIncludePowerModels, 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) {
builder = new UidBatteryConsumer.Builder(mCustomPowerComponentNames,
mIncludePowerModels, 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) {
builder = new UserBatteryConsumer.Builder(mCustomPowerComponentNames,
mIncludePowerModels, 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 (mUserBatteryConsumerBuilders.size() != 0
|| !stats.getUserBatteryConsumers().isEmpty()) {
throw new UnsupportedOperationException(
"Combining UserBatteryConsumers is not supported");
}
mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound;
mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound;
mDischargePercentage += stats.mDischargePercentage;
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;
}
}
}