| /* |
| * 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); |
| } |
| } |
| } |