script-astra/Android/Sdk/sources/android-35/com/android/internal/os/PowerStats.java

719 lines
30 KiB
Java
Raw Permalink Normal View History

2025-01-20 15:15:20 +00:00
/*
* Copyright (C) 2023 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 com.android.internal.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
* details.
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class PowerStats {
private static final String TAG = "PowerStats";
private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER =
new BatteryStatsHistory.VarintParceler();
private static final byte PARCEL_FORMAT_VERSION = 2;
private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF;
private static final int PARCEL_FORMAT_VERSION_SHIFT =
Integer.numberOfTrailingZeros(PARCEL_FORMAT_VERSION_MASK);
private static final int STATS_ARRAY_LENGTH_MASK = 0x0000FF00;
private static final int STATS_ARRAY_LENGTH_SHIFT =
Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK);
public static final int MAX_STATS_ARRAY_LENGTH =
(1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1;
private static final int STATE_STATS_ARRAY_LENGTH_MASK = 0x00FF0000;
private static final int STATE_STATS_ARRAY_LENGTH_SHIFT =
Integer.numberOfTrailingZeros(STATE_STATS_ARRAY_LENGTH_MASK);
public static final int MAX_STATE_STATS_ARRAY_LENGTH =
(1 << Integer.bitCount(STATE_STATS_ARRAY_LENGTH_MASK)) - 1;
private static final int UID_STATS_ARRAY_LENGTH_MASK = 0xFF000000;
private static final int UID_STATS_ARRAY_LENGTH_SHIFT =
Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK);
public static final int MAX_UID_STATS_ARRAY_LENGTH =
(1 << Integer.bitCount(UID_STATS_ARRAY_LENGTH_MASK)) - 1;
/**
* Descriptor of the stats collected for a given power component (e.g. CPU, WiFi etc).
* This descriptor is used for storing PowerStats and can also be used by power models
* to adjust the algorithm in accordance with the stats available on the device.
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class Descriptor {
public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
public static final String EXTRA_UID_STATS_FORMAT = "format-uid";
public static final String XML_TAG_DESCRIPTOR = "descriptor";
private static final String XML_ATTR_ID = "id";
private static final String XML_ATTR_NAME = "name";
private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length";
private static final String XML_TAG_STATE = "state";
private static final String XML_ATTR_STATE_KEY = "key";
private static final String XML_ATTR_STATE_LABEL = "label";
private static final String XML_ATTR_STATE_STATS_ARRAY_LENGTH = "state-stats-array-length";
private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length";
private static final String XML_TAG_EXTRAS = "extras";
/**
* {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates
* to; or a custom power component ID (if the value
* is &gt;= {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}).
*/
public final int powerComponentId;
public final String name;
/**
* Stats for the power component, such as the total usage time.
*/
public final int statsArrayLength;
/**
* Map of device state codes to their corresponding human-readable labels.
*/
public final SparseArray<String> stateLabels;
/**
* Stats for a specific state of the power component, e.g. "mobile radio in the 5G mode"
*/
public final int stateStatsArrayLength;
/**
* Stats for the usage of this power component by a specific UID (app)
*/
public final int uidStatsArrayLength;
/**
* Extra parameters specific to the power component, e.g. the availability of power
* monitors.
*/
public final PersistableBundle extras;
private PowerStatsFormatter mDeviceStatsFormatter;
private PowerStatsFormatter mStateStatsFormatter;
private PowerStatsFormatter mUidStatsFormatter;
public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
int statsArrayLength, @Nullable SparseArray<String> stateLabels,
int stateStatsArrayLength, int uidStatsArrayLength,
@NonNull PersistableBundle extras) {
this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId),
statsArrayLength, stateLabels, stateStatsArrayLength, uidStatsArrayLength,
extras);
}
public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
@Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
int uidStatsArrayLength, @NonNull PersistableBundle extras) {
if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
}
if (stateStatsArrayLength > MAX_STATE_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"stateStatsArrayLength is too high. Max = " + MAX_STATE_STATS_ARRAY_LENGTH);
}
if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH);
}
this.powerComponentId = customPowerComponentId;
this.name = name;
this.statsArrayLength = statsArrayLength;
this.stateLabels = stateLabels != null ? stateLabels : new SparseArray<>();
this.stateStatsArrayLength = stateStatsArrayLength;
this.uidStatsArrayLength = uidStatsArrayLength;
this.extras = extras;
}
/**
* Returns a custom formatter for this type of power stats.
*/
public PowerStatsFormatter getDeviceStatsFormatter() {
if (mDeviceStatsFormatter == null) {
mDeviceStatsFormatter = new PowerStatsFormatter(
extras.getString(EXTRA_DEVICE_STATS_FORMAT));
}
return mDeviceStatsFormatter;
}
/**
* Returns a custom formatter for this type of power stats, specifically per-state stats.
*/
public PowerStatsFormatter getStateStatsFormatter() {
if (mStateStatsFormatter == null) {
mStateStatsFormatter = new PowerStatsFormatter(
extras.getString(EXTRA_STATE_STATS_FORMAT));
}
return mStateStatsFormatter;
}
/**
* Returns a custom formatter for this type of power stats, specifically per-UID stats.
*/
public PowerStatsFormatter getUidStatsFormatter() {
if (mUidStatsFormatter == null) {
mUidStatsFormatter = new PowerStatsFormatter(
extras.getString(EXTRA_UID_STATS_FORMAT));
}
return mUidStatsFormatter;
}
/**
* Returns the label associated with the give state key, e.g. "5G-high" for the
* state of Mobile Radio representing the 5G mode and high signal power.
*/
public String getStateLabel(int key) {
String label = stateLabels.get(key);
if (label != null) {
return label;
}
return name + "-" + Integer.toHexString(key);
}
/**
* Writes the Descriptor into the parcel.
*/
public void writeSummaryToParcel(Parcel parcel) {
int firstWord = ((PARCEL_FORMAT_VERSION << PARCEL_FORMAT_VERSION_SHIFT)
& PARCEL_FORMAT_VERSION_MASK)
| ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT)
& STATS_ARRAY_LENGTH_MASK)
| ((stateStatsArrayLength << STATE_STATS_ARRAY_LENGTH_SHIFT)
& STATE_STATS_ARRAY_LENGTH_MASK)
| ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT)
& UID_STATS_ARRAY_LENGTH_MASK);
parcel.writeInt(firstWord);
parcel.writeInt(powerComponentId);
parcel.writeString(name);
parcel.writeInt(stateLabels.size());
for (int i = 0, size = stateLabels.size(); i < size; i++) {
parcel.writeInt(stateLabels.keyAt(i));
parcel.writeString(stateLabels.valueAt(i));
}
extras.writeToParcel(parcel, 0);
}
/**
* Reads a Descriptor from the parcel. If the parcel has an incompatible format,
* returns null.
*/
@Nullable
public static Descriptor readSummaryFromParcel(Parcel parcel) {
int firstWord = parcel.readInt();
int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;
if (version != PARCEL_FORMAT_VERSION) {
Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "
+ "changed from " + version + " to " + PARCEL_FORMAT_VERSION);
return null;
}
int statsArrayLength =
(firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT;
int stateStatsArrayLength =
(firstWord & STATE_STATS_ARRAY_LENGTH_MASK) >>> STATE_STATS_ARRAY_LENGTH_SHIFT;
int uidStatsArrayLength =
(firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT;
int powerComponentId = parcel.readInt();
String name = parcel.readString();
int stateLabelCount = parcel.readInt();
SparseArray<String> stateLabels = new SparseArray<>(stateLabelCount);
for (int i = stateLabelCount; i > 0; i--) {
int key = parcel.readInt();
String label = parcel.readString();
stateLabels.put(key, label);
}
PersistableBundle extras = parcel.readPersistableBundle();
return new Descriptor(powerComponentId, name, statsArrayLength, stateLabels,
stateStatsArrayLength, uidStatsArrayLength, extras);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Descriptor)) return false;
Descriptor that = (Descriptor) o;
return powerComponentId == that.powerComponentId
&& statsArrayLength == that.statsArrayLength
&& stateLabels.contentEquals(that.stateLabels)
&& stateStatsArrayLength == that.stateStatsArrayLength
&& uidStatsArrayLength == that.uidStatsArrayLength
&& Objects.equals(name, that.name)
&& extras.size() == that.extras.size() // Unparcel the Parcel if not yet
&& Bundle.kindofEquals(extras,
that.extras); // Since the Parcel is now unparceled, do a deep comparison
}
/**
* Stores contents in an XML doc.
*/
public void writeXml(TypedXmlSerializer serializer) throws IOException {
serializer.startTag(null, XML_TAG_DESCRIPTOR);
serializer.attributeInt(null, XML_ATTR_ID, powerComponentId);
serializer.attribute(null, XML_ATTR_NAME, name);
serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength);
serializer.attributeInt(null, XML_ATTR_STATE_STATS_ARRAY_LENGTH, stateStatsArrayLength);
serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength);
for (int i = stateLabels.size() - 1; i >= 0; i--) {
serializer.startTag(null, XML_TAG_STATE);
serializer.attributeInt(null, XML_ATTR_STATE_KEY, stateLabels.keyAt(i));
serializer.attribute(null, XML_ATTR_STATE_LABEL, stateLabels.valueAt(i));
serializer.endTag(null, XML_TAG_STATE);
}
try {
serializer.startTag(null, XML_TAG_EXTRAS);
extras.saveToXml(serializer);
serializer.endTag(null, XML_TAG_EXTRAS);
} catch (XmlPullParserException e) {
throw new IOException(e);
}
serializer.endTag(null, XML_TAG_DESCRIPTOR);
}
/**
* Creates a Descriptor by parsing an XML doc. The parser is expected to be positioned
* on or before the opening "descriptor" tag.
*/
public static Descriptor createFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
int powerComponentId = -1;
String name = null;
int statsArrayLength = 0;
SparseArray<String> stateLabels = new SparseArray<>();
int stateStatsArrayLength = 0;
int uidStatsArrayLength = 0;
PersistableBundle extras = null;
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT
&& !(eventType == XmlPullParser.END_TAG
&& parser.getName().equals(XML_TAG_DESCRIPTOR))) {
if (eventType == XmlPullParser.START_TAG) {
switch (parser.getName()) {
case XML_TAG_DESCRIPTOR:
powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID);
name = parser.getAttributeValue(null, XML_ATTR_NAME);
statsArrayLength = parser.getAttributeInt(null,
XML_ATTR_STATS_ARRAY_LENGTH);
stateStatsArrayLength = parser.getAttributeInt(null,
XML_ATTR_STATE_STATS_ARRAY_LENGTH);
uidStatsArrayLength = parser.getAttributeInt(null,
XML_ATTR_UID_STATS_ARRAY_LENGTH);
break;
case XML_TAG_STATE:
int value = parser.getAttributeInt(null, XML_ATTR_STATE_KEY);
String label = parser.getAttributeValue(null, XML_ATTR_STATE_LABEL);
stateLabels.put(value, label);
break;
case XML_TAG_EXTRAS:
extras = PersistableBundle.restoreFromXml(parser);
break;
}
}
eventType = parser.next();
}
if (powerComponentId == -1) {
return null;
} else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
return new Descriptor(powerComponentId, name, statsArrayLength,
stateLabels, stateStatsArrayLength, uidStatsArrayLength, extras);
} else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) {
return new Descriptor(powerComponentId, statsArrayLength, stateLabels,
stateStatsArrayLength, uidStatsArrayLength, extras);
} else {
Slog.e(TAG, "Unrecognized power component: " + powerComponentId);
return null;
}
}
@Override
public int hashCode() {
return Objects.hash(powerComponentId);
}
@Override
public String toString() {
if (extras != null) {
extras.size(); // Unparcel
}
return "PowerStats.Descriptor{"
+ "powerComponentId=" + powerComponentId
+ ", name='" + name + '\''
+ ", statsArrayLength=" + statsArrayLength
+ ", stateStatsArrayLength=" + stateStatsArrayLength
+ ", stateLabels=" + stateLabels
+ ", uidStatsArrayLength=" + uidStatsArrayLength
+ ", extras=" + extras
+ '}';
}
}
/**
* A registry for all supported power component types (e.g. CPU, WiFi).
*/
public static class DescriptorRegistry {
private final SparseArray<Descriptor> mDescriptors = new SparseArray<>();
/**
* Adds the specified descriptor to the registry. If the registry already
* contained a descriptor for the same power component, then the new one replaces
* the old one.
*/
public void register(Descriptor descriptor) {
mDescriptors.put(descriptor.powerComponentId, descriptor);
}
/**
* @param powerComponentId either a BatteryConsumer.PowerComponent or a custom power
* component ID
*/
public Descriptor get(int powerComponentId) {
return mDescriptors.get(powerComponentId);
}
}
public final Descriptor descriptor;
/**
* Duration, in milliseconds, covered by this snapshot.
*/
public long durationMs;
/**
* Device-wide stats.
*/
public long[] stats;
/**
* Device-wide mode stats, used when the power component can operate in different modes,
* e.g. RATs such as LTE and 5G.
*/
public final SparseArray<long[]> stateStats = new SparseArray<>();
/**
* Per-UID CPU stats.
*/
public final SparseArray<long[]> uidStats = new SparseArray<>();
public PowerStats(Descriptor descriptor) {
this.descriptor = descriptor;
stats = new long[descriptor.statsArrayLength];
}
/**
* Writes the object into the parcel.
*/
public void writeToParcel(Parcel parcel) {
int lengthPos = parcel.dataPosition();
parcel.writeInt(0); // Placeholder for length
int startPos = parcel.dataPosition();
parcel.writeInt(descriptor.powerComponentId);
parcel.writeLong(durationMs);
VARINT_PARCELER.writeLongArray(parcel, stats);
if (descriptor.stateStatsArrayLength != 0) {
parcel.writeInt(stateStats.size());
for (int i = 0; i < stateStats.size(); i++) {
parcel.writeInt(stateStats.keyAt(i));
VARINT_PARCELER.writeLongArray(parcel, stateStats.valueAt(i));
}
}
parcel.writeInt(uidStats.size());
for (int i = 0; i < uidStats.size(); i++) {
parcel.writeInt(uidStats.keyAt(i));
VARINT_PARCELER.writeLongArray(parcel, uidStats.valueAt(i));
}
int endPos = parcel.dataPosition();
parcel.setDataPosition(lengthPos);
parcel.writeInt(endPos - startPos);
parcel.setDataPosition(endPos);
}
/**
* Reads a PowerStats object from the supplied Parcel. If the parcel has an incompatible
* format, returns null.
*/
@Nullable
public static PowerStats readFromParcel(Parcel parcel, DescriptorRegistry registry) {
int length = parcel.readInt();
int startPos = parcel.dataPosition();
int endPos = startPos + length;
try {
int powerComponentId = parcel.readInt();
Descriptor descriptor = registry.get(powerComponentId);
if (descriptor == null) {
Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);
return null;
}
PowerStats stats = new PowerStats(descriptor);
stats.durationMs = parcel.readLong();
stats.stats = new long[descriptor.statsArrayLength];
VARINT_PARCELER.readLongArray(parcel, stats.stats);
if (descriptor.stateStatsArrayLength != 0) {
int count = parcel.readInt();
for (int i = 0; i < count; i++) {
int state = parcel.readInt();
long[] stateStats = new long[descriptor.stateStatsArrayLength];
VARINT_PARCELER.readLongArray(parcel, stateStats);
stats.stateStats.put(state, stateStats);
}
}
int uidCount = parcel.readInt();
for (int i = 0; i < uidCount; i++) {
int uid = parcel.readInt();
long[] uidStats = new long[descriptor.uidStatsArrayLength];
VARINT_PARCELER.readLongArray(parcel, uidStats);
stats.uidStats.put(uid, uidStats);
}
if (parcel.dataPosition() != endPos) {
Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length
+ ", actual length: " + (parcel.dataPosition() - startPos));
return null;
}
return stats;
} finally {
// Unconditionally skip to the end of the written data, even if the actual parcel
// format is incompatible
if (endPos > parcel.dataPosition()) {
if (endPos >= parcel.dataSize()) {
throw new IndexOutOfBoundsException(
"PowerStats end position: " + endPos + " is outside the parcel bounds: "
+ parcel.dataSize());
}
parcel.setDataPosition(endPos);
}
}
}
/**
* Formats the stats as a string suitable to be included in the Battery History dump.
*/
public String formatForBatteryHistory(String uidPrefix) {
StringBuilder sb = new StringBuilder();
sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
if (stats.length > 0) {
sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
}
if (descriptor.stateStatsArrayLength != 0) {
PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
for (int i = 0; i < stateStats.size(); i++) {
sb.append(" (");
sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
sb.append(") ");
sb.append(formatter.format(stateStats.valueAt(i)));
}
}
PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
for (int i = 0; i < uidStats.size(); i++) {
sb.append(uidPrefix)
.append(UserHandle.formatUid(uidStats.keyAt(i)))
.append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
}
return sb.toString();
}
/**
* Prints the contents of the stats snapshot.
*/
public void dump(IndentingPrintWriter pw) {
pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
pw.increaseIndent();
pw.print("duration", durationMs).println();
if (descriptor.statsArrayLength != 0) {
pw.println(descriptor.getDeviceStatsFormatter().format(stats));
}
if (descriptor.stateStatsArrayLength != 0) {
PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
for (int i = 0; i < stateStats.size(); i++) {
pw.print(" (");
pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
pw.print(") ");
pw.print(formatter.format(stateStats.valueAt(i)));
pw.println();
}
}
PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
for (int i = 0; i < uidStats.size(); i++) {
pw.print("UID ");
pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
pw.print(": ");
pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
pw.println();
}
pw.decreaseIndent();
}
@Override
public String toString() {
return "PowerStats: " + formatForBatteryHistory(" UID ");
}
public static class PowerStatsFormatter {
private static class Section {
public String label;
public int position;
public int length;
public boolean optional;
public boolean typePower;
}
private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
private static final Pattern SECTION_PATTERN =
Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
private final List<Section> mSections;
public PowerStatsFormatter(String format) {
mSections = parseFormat(format);
}
/**
* Produces a formatted string representing the supplied array, with labels
* and other adornments specific to the power stats layout.
*/
public String format(long[] stats) {
return format(mSections, stats);
}
private List<Section> parseFormat(String format) {
if (format == null || format.isBlank()) {
return null;
}
ArrayList<Section> sections = new ArrayList<>();
Matcher matcher = SECTION_PATTERN.matcher(format);
for (int position = 0; position < format.length(); position = matcher.end()) {
if (!matcher.find() || matcher.start() != position) {
Slog.wtf(TAG, "Bad power stats format '" + format + "'");
return null;
}
Section section = new Section();
section.label = matcher.group(1);
section.position = Integer.parseUnsignedInt(matcher.group(2));
String length = matcher.group("L");
if (length != null) {
section.length = Integer.parseUnsignedInt(length);
} else {
section.length = 1;
}
String flags = matcher.group("F");
if (flags != null) {
for (int i = 0; i < flags.length(); i++) {
char flag = flags.charAt(i);
switch (flag) {
case '?':
section.optional = true;
break;
case 'p':
section.typePower = true;
break;
default:
Slog.e(TAG,
"Unsupported format option '" + flag + "' in " + format);
break;
}
}
}
sections.add(section);
}
return sections;
}
private String format(List<Section> sections, long[] stats) {
if (sections == null) {
return Arrays.toString(stats);
}
StringBuilder sb = new StringBuilder();
for (int i = 0, count = sections.size(); i < count; i++) {
Section section = sections.get(i);
if (section.length == 0) {
continue;
}
if (section.optional) {
boolean nonZero = false;
for (int offset = 0; offset < section.length; offset++) {
if (stats[section.position + offset] != 0) {
nonZero = true;
break;
}
}
if (!nonZero) {
continue;
}
}
if (!sb.isEmpty()) {
sb.append(' ');
}
sb.append(section.label).append(": ");
if (section.length != 1) {
sb.append('[');
}
for (int offset = 0; offset < section.length; offset++) {
if (offset != 0) {
sb.append(", ");
}
if (section.typePower) {
sb.append(BatteryStats.formatCharge(
stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
} else {
sb.append(stats[section.position + offset]);
}
}
if (section.length != 1) {
sb.append(']');
}
}
return sb.toString();
}
}
}