675 lines
30 KiB
Java
675 lines
30 KiB
Java
/*
|
|
* 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 static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED;
|
|
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
|
|
import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
|
|
import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
|
|
import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.util.proto.ProtoOutputStream;
|
|
|
|
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.io.PrintWriter;
|
|
|
|
/**
|
|
* Contains details of battery attribution data broken down to individual power drain types
|
|
* such as CPU, RAM, GPU etc.
|
|
*
|
|
* @hide
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
|
class PowerComponents {
|
|
private final BatteryConsumer.BatteryConsumerData mData;
|
|
|
|
PowerComponents(@NonNull Builder builder) {
|
|
mData = builder.mData;
|
|
}
|
|
|
|
PowerComponents(BatteryConsumer.BatteryConsumerData data) {
|
|
mData = data;
|
|
}
|
|
|
|
/**
|
|
* Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
|
|
*/
|
|
public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
|
|
if (dimensions.powerComponent != POWER_COMPONENT_ANY) {
|
|
return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
|
|
dimensions.processState).mPowerColumnIndex);
|
|
} else if (dimensions.processState != PROCESS_STATE_ANY) {
|
|
if (!mData.layout.processStateDataIncluded) {
|
|
throw new IllegalArgumentException(
|
|
"No data included in BatteryUsageStats for " + dimensions);
|
|
}
|
|
final BatteryConsumer.Key[] keys =
|
|
mData.layout.processStateKeys[dimensions.processState];
|
|
double totalPowerMah = 0;
|
|
for (int i = keys.length - 1; i >= 0; i--) {
|
|
totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex);
|
|
}
|
|
return totalPowerMah;
|
|
} else {
|
|
return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
|
|
*
|
|
* @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
|
|
* or {@link BatteryConsumer#getKeys} method.
|
|
* @return Amount of consumed power in mAh.
|
|
*/
|
|
public double getConsumedPower(@NonNull BatteryConsumer.Key key) {
|
|
return mData.getDouble(key.mPowerColumnIndex);
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of drain attributed to the specified custom drain type.
|
|
*
|
|
* @param componentId The ID of the custom power component.
|
|
* @return Amount of consumed power in mAh.
|
|
*/
|
|
public double getConsumedPowerForCustomComponent(int componentId) {
|
|
final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
|
|
if (index >= 0 && index < mData.layout.customPowerComponentCount) {
|
|
return mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + index);
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Unsupported custom power component ID: " + componentId);
|
|
}
|
|
}
|
|
|
|
public String getCustomPowerComponentName(int componentId) {
|
|
final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
|
|
if (index >= 0 && index < mData.layout.customPowerComponentCount) {
|
|
try {
|
|
return mData.layout.customPowerComponentNames[index];
|
|
} catch (ArrayIndexOutOfBoundsException e) {
|
|
throw new IllegalArgumentException(
|
|
"Unsupported custom power component ID: " + componentId);
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Unsupported custom power component ID: " + componentId);
|
|
}
|
|
}
|
|
|
|
@BatteryConsumer.PowerModel
|
|
int getPowerModel(BatteryConsumer.Key key) {
|
|
if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
|
|
throw new IllegalStateException(
|
|
"Power model IDs were not requested in the BatteryUsageStatsQuery");
|
|
}
|
|
return mData.getInt(key.mPowerModelColumnIndex);
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
|
|
*
|
|
* @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
|
|
* or {@link BatteryConsumer#getKeys} method.
|
|
* @return Amount of time in milliseconds.
|
|
*/
|
|
public long getUsageDurationMillis(BatteryConsumer.Key key) {
|
|
return mData.getLong(key.mDurationColumnIndex);
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of usage time attributed to the specified custom component.
|
|
*
|
|
* @param componentId The ID of the custom power component.
|
|
* @return Amount of time in milliseconds.
|
|
*/
|
|
public long getUsageDurationForCustomComponentMillis(int componentId) {
|
|
final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
|
|
if (index >= 0 && index < mData.layout.customPowerComponentCount) {
|
|
return mData.getLong(mData.layout.firstCustomUsageDurationColumn + index);
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Unsupported custom power component ID: " + componentId);
|
|
}
|
|
}
|
|
|
|
public void dump(PrintWriter pw, boolean skipEmptyComponents) {
|
|
String separator = "";
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
|
|
componentId++) {
|
|
for (BatteryConsumer.Key key: mData.getKeys(componentId)) {
|
|
final double componentPower = getConsumedPower(key);
|
|
final long durationMs = getUsageDurationMillis(key);
|
|
if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
|
|
continue;
|
|
}
|
|
|
|
sb.append(separator);
|
|
separator = " ";
|
|
sb.append(key.toShortString());
|
|
sb.append("=");
|
|
sb.append(BatteryStats.formatCharge(componentPower));
|
|
|
|
if (durationMs != 0) {
|
|
sb.append(" (");
|
|
BatteryStats.formatTimeMsNoSpace(sb, durationMs);
|
|
sb.append(")");
|
|
}
|
|
}
|
|
}
|
|
|
|
final int customComponentCount = mData.layout.customPowerComponentCount;
|
|
for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
|
|
customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
|
|
+ customComponentCount;
|
|
customComponentId++) {
|
|
final double customComponentPower =
|
|
getConsumedPowerForCustomComponent(customComponentId);
|
|
if (skipEmptyComponents && customComponentPower == 0) {
|
|
continue;
|
|
}
|
|
sb.append(separator);
|
|
separator = " ";
|
|
sb.append(getCustomPowerComponentName(customComponentId));
|
|
sb.append("=");
|
|
sb.append(BatteryStats.formatCharge(customComponentPower));
|
|
}
|
|
|
|
pw.print(sb);
|
|
}
|
|
|
|
/** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
|
|
boolean hasStatsProtoData() {
|
|
return writeStatsProtoImpl(null);
|
|
}
|
|
|
|
/** Writes all atoms.proto POWER_COMPONENTS for this PowerComponents to the given proto. */
|
|
void writeStatsProto(@NonNull ProtoOutputStream proto) {
|
|
writeStatsProtoImpl(proto);
|
|
}
|
|
|
|
/**
|
|
* Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto,
|
|
* and writes it to the given proto if it is non-null.
|
|
*/
|
|
private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) {
|
|
boolean interestingData = false;
|
|
|
|
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
|
|
componentId++) {
|
|
|
|
final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
|
|
for (BatteryConsumer.Key key : keys) {
|
|
final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
|
|
final long durationMs = getUsageDurationMillis(key);
|
|
|
|
if (powerDeciCoulombs == 0 && durationMs == 0) {
|
|
// No interesting data. Make sure not to even write the COMPONENT int.
|
|
continue;
|
|
}
|
|
|
|
interestingData = true;
|
|
if (proto == null) {
|
|
// We're just asked whether there is data, not to actually write it.
|
|
// And there is.
|
|
return true;
|
|
}
|
|
|
|
if (key.processState == PROCESS_STATE_ANY) {
|
|
writePowerComponentUsage(proto,
|
|
BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
|
|
componentId, powerDeciCoulombs, durationMs);
|
|
} else {
|
|
writePowerUsageSlice(proto, componentId, powerDeciCoulombs, durationMs,
|
|
key.processState);
|
|
}
|
|
}
|
|
}
|
|
for (int idx = 0; idx < mData.layout.customPowerComponentCount; idx++) {
|
|
final int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + idx;
|
|
final long powerDeciCoulombs =
|
|
convertMahToDeciCoulombs(getConsumedPowerForCustomComponent(componentId));
|
|
final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
|
|
|
|
if (powerDeciCoulombs == 0 && durationMs == 0) {
|
|
// No interesting data. Make sure not to even write the COMPONENT int.
|
|
continue;
|
|
}
|
|
|
|
interestingData = true;
|
|
if (proto == null) {
|
|
// We're just asked whether there is data, not to actually write it. And there is.
|
|
return true;
|
|
}
|
|
|
|
writePowerComponentUsage(proto,
|
|
BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
|
|
componentId, powerDeciCoulombs, durationMs);
|
|
}
|
|
return interestingData;
|
|
}
|
|
|
|
private void writePowerUsageSlice(ProtoOutputStream proto, int componentId,
|
|
long powerDeciCoulombs, long durationMs, int processState) {
|
|
final long slicesToken =
|
|
proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.SLICES);
|
|
writePowerComponentUsage(proto,
|
|
BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
|
|
.POWER_COMPONENT,
|
|
componentId, powerDeciCoulombs, durationMs);
|
|
|
|
final int procState;
|
|
switch (processState) {
|
|
case BatteryConsumer.PROCESS_STATE_FOREGROUND:
|
|
procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
|
|
.FOREGROUND;
|
|
break;
|
|
case BatteryConsumer.PROCESS_STATE_BACKGROUND:
|
|
procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
|
|
.BACKGROUND;
|
|
break;
|
|
case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
|
|
procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
|
|
.FOREGROUND_SERVICE;
|
|
break;
|
|
case BatteryConsumer.PROCESS_STATE_CACHED:
|
|
procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
|
|
.CACHED;
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException("Unknown process state: " + processState);
|
|
}
|
|
|
|
proto.write(BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
|
|
.PROCESS_STATE, procState);
|
|
|
|
proto.end(slicesToken);
|
|
}
|
|
|
|
private void writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId,
|
|
long powerDeciCoulombs, long durationMs) {
|
|
final long token = proto.start(tag);
|
|
proto.write(
|
|
BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
|
|
.COMPONENT,
|
|
componentId);
|
|
proto.write(
|
|
BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
|
|
.POWER_DECI_COULOMBS,
|
|
powerDeciCoulombs);
|
|
proto.write(
|
|
BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
|
|
.DURATION_MILLIS,
|
|
durationMs);
|
|
proto.end(token);
|
|
}
|
|
|
|
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 BatteryConsumer.Key[] keys = mData.getKeys(componentId);
|
|
for (BatteryConsumer.Key key : keys) {
|
|
final double powerMah = getConsumedPower(key);
|
|
final long durationMs = getUsageDurationMillis(key);
|
|
if (powerMah == 0 && durationMs == 0) {
|
|
continue;
|
|
}
|
|
|
|
serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
|
|
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
|
|
if (key.processState != PROCESS_STATE_UNSPECIFIED) {
|
|
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
|
|
key.processState);
|
|
}
|
|
if (powerMah != 0) {
|
|
serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
|
|
}
|
|
if (durationMs != 0) {
|
|
serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
|
|
}
|
|
if (mData.layout.powerModelsIncluded) {
|
|
serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
|
|
getPowerModel(key));
|
|
}
|
|
serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
|
|
}
|
|
}
|
|
|
|
final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
|
|
+ mData.layout.customPowerComponentCount;
|
|
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;
|
|
int processState = PROCESS_STATE_UNSPECIFIED;
|
|
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_PROCESS_STATE:
|
|
processState = 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;
|
|
}
|
|
}
|
|
final BatteryConsumer.Key key =
|
|
builder.mData.getKey(componentId, processState);
|
|
builder.setConsumedPower(key, powerMah, model);
|
|
builder.setUsageDurationMillis(key, 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.
|
|
*/
|
|
static final class Builder {
|
|
private static final byte POWER_MODEL_UNINITIALIZED = -1;
|
|
|
|
private final BatteryConsumer.BatteryConsumerData mData;
|
|
private final double mMinConsumedPowerThreshold;
|
|
|
|
Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
|
|
mData = data;
|
|
mMinConsumedPowerThreshold = minConsumedPowerThreshold;
|
|
for (BatteryConsumer.Key[] keys : mData.layout.keys) {
|
|
for (BatteryConsumer.Key key : keys) {
|
|
if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
|
|
mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
|
|
int powerModel) {
|
|
mData.putDouble(key.mPowerColumnIndex, componentPower);
|
|
if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
|
|
mData.putInt(key.mPowerModelColumnIndex, powerModel);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower,
|
|
int powerModel) {
|
|
mData.putDouble(key.mPowerColumnIndex,
|
|
mData.getDouble(key.mPowerColumnIndex) + componentPower);
|
|
if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
|
|
mData.putInt(key.mPowerModelColumnIndex, powerModel);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the amount of drain attributed to the specified custom drain type.
|
|
*
|
|
* @param componentId The ID of the custom power component.
|
|
* @param componentPower Amount of consumed power in mAh.
|
|
*/
|
|
@NonNull
|
|
public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
|
|
final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
|
|
if (index < 0 || index >= mData.layout.customPowerComponentCount) {
|
|
throw new IllegalArgumentException(
|
|
"Unsupported custom power component ID: " + componentId);
|
|
}
|
|
mData.putDouble(mData.layout.firstCustomConsumedPowerColumn + index, componentPower);
|
|
return this;
|
|
}
|
|
|
|
@NonNull
|
|
public Builder setUsageDurationMillis(BatteryConsumer.Key key,
|
|
long componentUsageDurationMillis) {
|
|
mData.putLong(key.mDurationColumnIndex, componentUsageDurationMillis);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the amount of time used by the specified custom component.
|
|
*
|
|
* @param componentId The ID of the custom power component.
|
|
* @param componentUsageDurationMillis Amount of time in milliseconds.
|
|
*/
|
|
@NonNull
|
|
public Builder setUsageDurationForCustomComponentMillis(int componentId,
|
|
long componentUsageDurationMillis) {
|
|
final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
|
|
if (index < 0 || index >= mData.layout.customPowerComponentCount) {
|
|
throw new IllegalArgumentException(
|
|
"Unsupported custom power component ID: " + componentId);
|
|
}
|
|
|
|
mData.putLong(mData.layout.firstCustomUsageDurationColumn + index,
|
|
componentUsageDurationMillis);
|
|
return this;
|
|
}
|
|
|
|
public void addPowerAndDuration(PowerComponents.Builder other) {
|
|
addPowerAndDuration(other.mData);
|
|
}
|
|
|
|
public void addPowerAndDuration(PowerComponents other) {
|
|
addPowerAndDuration(other.mData);
|
|
}
|
|
|
|
private void addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData) {
|
|
if (mData.layout.customPowerComponentCount
|
|
!= otherData.layout.customPowerComponentCount) {
|
|
throw new IllegalArgumentException(
|
|
"Number of custom power components does not match: "
|
|
+ otherData.layout.customPowerComponentCount
|
|
+ ", expected: " + mData.layout.customPowerComponentCount);
|
|
}
|
|
|
|
for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0;
|
|
componentId--) {
|
|
final BatteryConsumer.Key[] keys = mData.layout.keys[componentId];
|
|
for (BatteryConsumer.Key key: keys) {
|
|
BatteryConsumer.Key otherKey = null;
|
|
for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) {
|
|
if (aKey.equals(key)) {
|
|
otherKey = aKey;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (otherKey == null) {
|
|
continue;
|
|
}
|
|
|
|
mData.putDouble(key.mPowerColumnIndex,
|
|
mData.getDouble(key.mPowerColumnIndex)
|
|
+ otherData.getDouble(otherKey.mPowerColumnIndex));
|
|
mData.putLong(key.mDurationColumnIndex,
|
|
mData.getLong(key.mDurationColumnIndex)
|
|
+ otherData.getLong(otherKey.mDurationColumnIndex));
|
|
|
|
if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
|
|
continue;
|
|
}
|
|
|
|
boolean undefined = false;
|
|
if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
|
|
undefined = true;
|
|
} else {
|
|
final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
|
|
int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
|
|
if (powerModel == POWER_MODEL_UNINITIALIZED) {
|
|
mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
|
|
} else if (powerModel != otherPowerModel
|
|
&& otherPowerModel != POWER_MODEL_UNINITIALIZED) {
|
|
undefined = true;
|
|
}
|
|
}
|
|
|
|
if (undefined) {
|
|
mData.putInt(key.mPowerModelColumnIndex,
|
|
BatteryConsumer.POWER_MODEL_UNDEFINED);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = mData.layout.customPowerComponentCount - 1; i >= 0; i--) {
|
|
final int powerColumnIndex = mData.layout.firstCustomConsumedPowerColumn + i;
|
|
final int otherPowerColumnIndex =
|
|
otherData.layout.firstCustomConsumedPowerColumn + i;
|
|
mData.putDouble(powerColumnIndex,
|
|
mData.getDouble(powerColumnIndex) + otherData.getDouble(
|
|
otherPowerColumnIndex));
|
|
|
|
final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i;
|
|
final int otherDurationColumnIndex =
|
|
otherData.layout.firstCustomUsageDurationColumn + i;
|
|
mData.putLong(usageColumnIndex,
|
|
mData.getLong(usageColumnIndex) + otherData.getLong(
|
|
otherDurationColumnIndex)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the total power accumulated by this builder so far. It may change
|
|
* by the time the {@code build()} method is called.
|
|
*/
|
|
public double getTotalPower() {
|
|
double totalPowerMah = 0;
|
|
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
|
|
componentId++) {
|
|
totalPowerMah += mData.getDouble(
|
|
mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex);
|
|
}
|
|
for (int i = 0; i < mData.layout.customPowerComponentCount; i++) {
|
|
totalPowerMah += mData.getDouble(
|
|
mData.layout.firstCustomConsumedPowerColumn + i);
|
|
}
|
|
return totalPowerMah;
|
|
}
|
|
|
|
/**
|
|
* Creates a read-only object out of the Builder values.
|
|
*/
|
|
@NonNull
|
|
public PowerComponents build() {
|
|
for (BatteryConsumer.Key[] keys : mData.layout.keys) {
|
|
for (BatteryConsumer.Key key : keys) {
|
|
if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
|
|
if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
|
|
mData.putInt(key.mPowerModelColumnIndex,
|
|
BatteryConsumer.POWER_MODEL_UNDEFINED);
|
|
}
|
|
}
|
|
|
|
if (mMinConsumedPowerThreshold != 0) {
|
|
if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
|
|
mData.putDouble(key.mPowerColumnIndex, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mData.getDouble(mData.layout.totalConsumedPowerColumnIndex) == 0) {
|
|
mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
|
|
}
|
|
return new PowerComponents(this);
|
|
}
|
|
}
|
|
}
|