Convert BatteryUsageStats to and from XML

Bug: 187223764
Test: atest FrameworksCoreTests:BatteryUsageStatsTest#testXml
Change-Id: I6aec09bfb87c1003ede1b24cce8cf474675d2448
Merged-In: I6aec09bfb87c1003ede1b24cce8cf474675d2448
(cherry picked from commit 4b8a26bcaa)
This commit is contained in:
Dmitri Plotnikov
2021-05-17 17:45:50 -07:00
parent 36e0ae27a9
commit d82aa7e5c4
6 changed files with 425 additions and 0 deletions

View File

@@ -17,7 +17,13 @@
package android.os;
import android.annotation.NonNull;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.PrintWriter;
/**
@@ -72,6 +78,43 @@ public final class AggregateBatteryConsumer extends BatteryConsumer implements P
return mConsumedPowerMah;
}
/** Serializes this object to XML */
void writeToXml(TypedXmlSerializer serializer,
@BatteryUsageStats.AggregateBatteryConsumerScope int scope) throws IOException {
serializer.startTag(null, BatteryUsageStats.XML_TAG_AGGREGATE);
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE, scope);
serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, mConsumedPowerMah);
mPowerComponents.writeToXml(serializer);
serializer.endTag(null, BatteryUsageStats.XML_TAG_AGGREGATE);
}
/** Parses an XML representation and populates the BatteryUsageStats builder */
static void parseXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder)
throws XmlPullParserException, IOException {
final int scope = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_SCOPE);
final Builder consumerBuilder = builder.getAggregateBatteryConsumerBuilder(scope);
int eventType = parser.getEventType();
if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(
BatteryUsageStats.XML_TAG_AGGREGATE)) {
throw new XmlPullParserException("Invalid XML parser state");
}
consumerBuilder.setConsumedPower(
parser.getAttributeDouble(null, BatteryUsageStats.XML_ATTR_POWER));
while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
BatteryUsageStats.XML_TAG_AGGREGATE))
&& eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder);
}
}
eventType = parser.next();
}
}
/**
* Builder for DeviceBatteryConsumer.
*/

View File

@@ -20,12 +20,18 @@ 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;
@@ -78,6 +84,34 @@ public final class BatteryUsageStats implements Parcelable {
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;
@@ -576,6 +610,109 @@ public final class BatteryUsageStats implements Parcelable {
}
}
/** 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.
*/

View File

@@ -19,10 +19,16 @@ import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
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.util.Arrays;
@@ -302,6 +308,122 @@ class PowerComponents {
return interestingData;
}
void writeToXml(TypedXmlSerializer serializer) throws IOException {
serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
final double powerMah = getConsumedPower(componentId);
final long durationMs = getUsageDurationMillis(componentId);
if (powerMah == 0 && durationMs == 0) {
continue;
}
serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
if (powerMah != 0) {
serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
}
if (durationMs != 0) {
serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
}
if (mPowerModels != null) {
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
mPowerModels[componentId]);
}
serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
}
final int customComponentEnd =
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + mCustomPowerComponentCount;
for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
componentId < customComponentEnd;
componentId++) {
final double powerMah = getConsumedPowerForCustomComponent(componentId);
final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
if (powerMah == 0 && durationMs == 0) {
continue;
}
serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
if (powerMah != 0) {
serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
}
if (durationMs != 0) {
serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
}
serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
}
serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
}
static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)
throws XmlPullParserException, IOException {
int eventType = parser.getEventType();
if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(
BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
throw new XmlPullParserException("Invalid XML parser state");
}
while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
BatteryUsageStats.XML_TAG_POWER_COMPONENTS))
&& eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
switch (parser.getName()) {
case BatteryUsageStats.XML_TAG_COMPONENT: {
int componentId = -1;
double powerMah = 0;
long durationMs = 0;
int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
for (int i = 0; i < parser.getAttributeCount(); i++) {
switch (parser.getAttributeName(i)) {
case BatteryUsageStats.XML_ATTR_ID:
componentId = parser.getAttributeInt(i);
break;
case BatteryUsageStats.XML_ATTR_POWER:
powerMah = parser.getAttributeDouble(i);
break;
case BatteryUsageStats.XML_ATTR_DURATION:
durationMs = parser.getAttributeLong(i);
break;
case BatteryUsageStats.XML_ATTR_MODEL:
model = parser.getAttributeInt(i);
break;
}
}
builder.setConsumedPower(componentId, powerMah, model);
builder.setUsageDurationMillis(componentId, durationMs);
break;
}
case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: {
int componentId = -1;
double powerMah = 0;
long durationMs = 0;
for (int i = 0; i < parser.getAttributeCount(); i++) {
switch (parser.getAttributeName(i)) {
case BatteryUsageStats.XML_ATTR_ID:
componentId = parser.getAttributeInt(i);
break;
case BatteryUsageStats.XML_ATTR_POWER:
powerMah = parser.getAttributeDouble(i);
break;
case BatteryUsageStats.XML_ATTR_DURATION:
durationMs = parser.getAttributeLong(i);
break;
}
}
builder.setConsumedPowerForCustomComponent(componentId, powerMah);
builder.setUsageDurationForCustomComponentMillis(componentId, durationMs);
break;
}
}
}
eventType = parser.next();
}
}
/**
* Builder for PowerComponents.
*/

View File

@@ -20,9 +20,15 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.text.TextUtils;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
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;
@@ -144,6 +150,57 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela
return 0;
}
/** Serializes this object to XML */
void writeToXml(TypedXmlSerializer serializer) throws IOException {
if (getConsumedPower() == 0) {
return;
}
serializer.startTag(null, BatteryUsageStats.XML_TAG_UID);
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_UID, getUid());
if (!TextUtils.isEmpty(mPackageWithHighestDrain)) {
serializer.attribute(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE,
mPackageWithHighestDrain);
}
serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND,
mTimeInForegroundMs);
serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND,
mTimeInBackgroundMs);
mPowerComponents.writeToXml(serializer);
serializer.endTag(null, BatteryUsageStats.XML_TAG_UID);
}
/** Parses an XML representation and populates the BatteryUsageStats builder */
static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder)
throws XmlPullParserException, IOException {
final int uid = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_UID);
final UidBatteryConsumer.Builder consumerBuilder =
builder.getOrCreateUidBatteryConsumerBuilder(uid);
int eventType = parser.getEventType();
if (eventType != XmlPullParser.START_TAG
|| !parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) {
throw new XmlPullParserException("Invalid XML parser state");
}
consumerBuilder.setPackageWithHighestDrain(
parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE));
consumerBuilder.setTimeInStateMs(STATE_FOREGROUND,
parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND));
consumerBuilder.setTimeInStateMs(STATE_BACKGROUND,
parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND));
while (!(eventType == XmlPullParser.END_TAG
&& parser.getName().equals(BatteryUsageStats.XML_TAG_UID))
&& eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder);
}
}
eventType = parser.next();
}
}
/**
* Builder for UidBatteryConsumer.
*/

View File

@@ -17,9 +17,15 @@
package android.os;
import android.annotation.NonNull;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
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.util.ArrayList;
import java.util.List;
@@ -85,6 +91,42 @@ public class UserBatteryConsumer extends BatteryConsumer implements Parcelable {
return 0;
}
/** Serializes this object to XML */
void writeToXml(TypedXmlSerializer serializer) throws IOException {
if (getConsumedPower() == 0) {
return;
}
serializer.startTag(null, BatteryUsageStats.XML_TAG_USER);
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID, getUserId());
mPowerComponents.writeToXml(serializer);
serializer.endTag(null, BatteryUsageStats.XML_TAG_USER);
}
/** Parses an XML representation and populates the BatteryUsageStats builder */
static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder)
throws XmlPullParserException, IOException {
final int userId = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_USER_ID);
final UserBatteryConsumer.Builder consumerBuilder =
builder.getOrCreateUserBatteryConsumerBuilder(userId);
int eventType = parser.getEventType();
if (eventType != XmlPullParser.START_TAG
|| !parser.getName().equals(BatteryUsageStats.XML_TAG_USER)) {
throw new XmlPullParserException("Invalid XML parser state");
}
while (!(eventType == XmlPullParser.END_TAG
&& parser.getName().equals(BatteryUsageStats.XML_TAG_USER))
&& eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder);
}
}
eventType = parser.next();
}
}
/**
* Builder for UserBatteryConsumer.
*/

View File

@@ -30,6 +30,9 @@ import android.os.BatteryUsageStats;
import android.os.Parcel;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,8 +40,11 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -157,6 +163,24 @@ public class BatteryUsageStatsTest {
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
}
@Test
public void testXml() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
TypedXmlSerializer serializer = Xml.newBinarySerializer();
serializer.setOutput(out, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
final BatteryUsageStats stats = buildBatteryUsageStats1(true).build();
stats.writeXml(serializer);
serializer.endDocument();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
TypedXmlPullParser parser = Xml.newBinaryPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser);
assertBatteryUsageStats1(fromXml, true);
}
private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) {
final MockClocks clocks = new MockClocks();
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);