| /* |
| * 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.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; |
| |
| /** |
| * Contains details of battery attribution data broken down to individual power drain types |
| * such as CPU, RAM, GPU etc. |
| * |
| * @hide |
| */ |
| class PowerComponents { |
| private static final int CUSTOM_POWER_COMPONENT_OFFSET = BatteryConsumer.POWER_COMPONENT_COUNT |
| - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; |
| |
| private final double mConsumedPowerMah; |
| @NonNull |
| private final double[] mPowerComponentsMah; |
| @NonNull |
| private final long[] mUsageDurationsMs; |
| private final int mCustomPowerComponentCount; |
| @Nullable |
| private final byte[] mPowerModels; |
| // Not written to Parcel and must be explicitly restored during the parent object's unparceling |
| private String[] mCustomPowerComponentNames; |
| |
| PowerComponents(@NonNull Builder builder) { |
| mCustomPowerComponentNames = builder.mCustomPowerComponentNames; |
| mCustomPowerComponentCount = mCustomPowerComponentNames.length; |
| mPowerComponentsMah = builder.mPowerComponentsMah; |
| mUsageDurationsMs = builder.mUsageDurationsMs; |
| mConsumedPowerMah = builder.getTotalPower(); |
| mPowerModels = builder.getPowerModels(); |
| } |
| |
| PowerComponents(@NonNull Parcel source) { |
| mConsumedPowerMah = source.readDouble(); |
| mCustomPowerComponentCount = source.readInt(); |
| mPowerComponentsMah = source.createDoubleArray(); |
| mUsageDurationsMs = source.createLongArray(); |
| if (source.readBoolean()) { |
| mPowerModels = new byte[BatteryConsumer.POWER_COMPONENT_COUNT]; |
| source.readByteArray(mPowerModels); |
| } else { |
| mPowerModels = null; |
| } |
| } |
| |
| /** Writes contents to Parcel */ |
| void writeToParcel(@NonNull Parcel dest, int flags) { |
| dest.writeDouble(mConsumedPowerMah); |
| dest.writeInt(mCustomPowerComponentCount); |
| dest.writeDoubleArray(mPowerComponentsMah); |
| dest.writeLongArray(mUsageDurationsMs); |
| if (mPowerModels != null) { |
| dest.writeBoolean(true); |
| dest.writeByteArray(mPowerModels); |
| } else { |
| dest.writeBoolean(false); |
| } |
| } |
| |
| /** |
| * Total power consumed by this consumer, in mAh. |
| */ |
| public double getConsumedPower() { |
| return mConsumedPowerMah; |
| } |
| |
| /** |
| * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc. |
| * |
| * @param componentId The ID of the power component, e.g. |
| * {@link BatteryConsumer#POWER_COMPONENT_CPU}. |
| * @return Amount of consumed power in mAh. |
| */ |
| public double getConsumedPower(@BatteryConsumer.PowerComponent int componentId) { |
| if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) { |
| throw new IllegalArgumentException( |
| "Unsupported power component ID: " + componentId); |
| } |
| try { |
| return mPowerComponentsMah[componentId]; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException("Unsupported power component ID: " + componentId); |
| } |
| } |
| |
| /** |
| * 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) { |
| if (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID |
| && componentId < BatteryConsumer.LAST_CUSTOM_POWER_COMPONENT_ID) { |
| try { |
| return mPowerComponentsMah[CUSTOM_POWER_COMPONENT_OFFSET + componentId]; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| } else { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| } |
| |
| void setCustomPowerComponentNames(String[] customPowerComponentNames) { |
| mCustomPowerComponentNames = customPowerComponentNames; |
| } |
| |
| public String getCustomPowerComponentName(int componentId) { |
| if (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID |
| && componentId < BatteryConsumer.LAST_CUSTOM_POWER_COMPONENT_ID) { |
| try { |
| return mCustomPowerComponentNames[componentId |
| - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID]; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| } else { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| } |
| |
| public boolean hasPowerModels() { |
| return mPowerModels != null; |
| } |
| |
| @BatteryConsumer.PowerModel |
| int getPowerModel(@BatteryConsumer.PowerComponent int component) { |
| if (!hasPowerModels()) { |
| throw new IllegalStateException( |
| "Power model IDs were not requested in the BatteryUsageStatsQuery"); |
| } |
| return mPowerModels[component]; |
| } |
| |
| /** |
| * Returns the amount of time used by the specified component, e.g. CPU, WiFi etc. |
| * |
| * @param componentId The ID of the power component, e.g. |
| * {@link BatteryConsumer#POWER_COMPONENT_CPU}. |
| * @return Amount of time in milliseconds. |
| */ |
| public long getUsageDurationMillis(@BatteryConsumer.PowerComponent int componentId) { |
| if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) { |
| throw new IllegalArgumentException( |
| "Unsupported power component ID: " + componentId); |
| } |
| try { |
| return mUsageDurationsMs[componentId]; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException("Unsupported power component ID: " + componentId); |
| } |
| } |
| |
| /** |
| * 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) { |
| if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| try { |
| return mUsageDurationsMs[CUSTOM_POWER_COMPONENT_OFFSET + componentId]; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| } |
| |
| public int getCustomPowerComponentCount() { |
| return mCustomPowerComponentCount; |
| } |
| |
| /** |
| * Returns the largest usage duration among all power components. |
| */ |
| public long getMaxComponentUsageDurationMillis() { |
| long max = 0; |
| for (int i = mUsageDurationsMs.length - 1; i >= 0; i--) { |
| if (mUsageDurationsMs[i] > max) { |
| max = mUsageDurationsMs[i]; |
| } |
| } |
| return max; |
| } |
| |
| public void dump(PrintWriter pw, boolean skipEmptyComponents) { |
| String separator = ""; |
| for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; |
| componentId++) { |
| final double componentPower = getConsumedPower(componentId); |
| if (skipEmptyComponents && componentPower == 0) { |
| continue; |
| } |
| pw.print(separator); separator = " "; |
| pw.print(BatteryConsumer.powerComponentIdToString(componentId)); |
| pw.print("="); |
| PowerCalculator.printPowerMah(pw, componentPower); |
| } |
| |
| final int customComponentCount = getCustomPowerComponentCount(); |
| 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; |
| } |
| pw.print(separator); separator = " "; |
| pw.print(getCustomPowerComponentName(customComponentId)); |
| pw.print("="); |
| PowerCalculator.printPowerMah(pw, customComponentPower); |
| } |
| } |
| |
| /** 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 idx = 0; idx < mPowerComponentsMah.length; idx++) { |
| final int componentId = idx < BatteryConsumer.POWER_COMPONENT_COUNT ? |
| idx : idx - CUSTOM_POWER_COMPONENT_OFFSET; |
| final long powerDeciCoulombs = convertMahToDeciCoulombs(mPowerComponentsMah[idx]); |
| final long durationMs = mUsageDurationsMs[idx]; |
| |
| 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; |
| } |
| |
| final long token = |
| proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS); |
| 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); |
| } |
| 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. |
| */ |
| static final class Builder { |
| private static final byte POWER_MODEL_UNINITIALIZED = -1; |
| |
| private final double[] mPowerComponentsMah; |
| private final String[] mCustomPowerComponentNames; |
| private final long[] mUsageDurationsMs; |
| private final byte[] mPowerModels; |
| |
| Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels) { |
| mCustomPowerComponentNames = customPowerComponentNames; |
| int powerComponentCount = |
| BatteryConsumer.POWER_COMPONENT_COUNT + mCustomPowerComponentNames.length; |
| mPowerComponentsMah = new double[powerComponentCount]; |
| mUsageDurationsMs = new long[powerComponentCount]; |
| if (includePowerModels) { |
| mPowerModels = new byte[BatteryConsumer.POWER_COMPONENT_COUNT]; |
| Arrays.fill(mPowerModels, POWER_MODEL_UNINITIALIZED); |
| } else { |
| mPowerModels = null; |
| } |
| } |
| |
| /** |
| * Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc. |
| * |
| * @param componentId The ID of the power component, e.g. |
| * {@link BatteryConsumer#POWER_COMPONENT_CPU}. |
| * @param componentPower Amount of consumed power in mAh. |
| */ |
| @NonNull |
| public Builder setConsumedPower(@BatteryConsumer.PowerComponent int componentId, |
| double componentPower, @BatteryConsumer.PowerModel int powerModel) { |
| if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) { |
| throw new IllegalArgumentException( |
| "Unsupported power component ID: " + componentId); |
| } |
| try { |
| mPowerComponentsMah[componentId] = componentPower; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException( |
| "Unsupported power component ID: " + componentId); |
| } |
| if (mPowerModels != null) { |
| mPowerModels[componentId] = (byte) 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) { |
| if (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID |
| && componentId < BatteryConsumer.LAST_CUSTOM_POWER_COMPONENT_ID) { |
| try { |
| mPowerComponentsMah[CUSTOM_POWER_COMPONENT_OFFSET + componentId] = |
| componentPower; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| } else { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| return this; |
| } |
| |
| /** |
| * Sets the amount of time used by the specified component, e.g. CPU, WiFi etc. |
| * |
| * @param componentId The ID of the power component, e.g. |
| * {@link BatteryConsumer#POWER_COMPONENT_CPU}. |
| * @param componentUsageDurationMillis Amount of time in milliseconds. |
| */ |
| @NonNull |
| public Builder setUsageDurationMillis(@BatteryConsumer.PowerComponent int componentId, |
| long componentUsageDurationMillis) { |
| if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) { |
| throw new IllegalArgumentException( |
| "Unsupported power component ID: " + componentId); |
| } |
| try { |
| mUsageDurationsMs[componentId] = componentUsageDurationMillis; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException( |
| "Unsupported power component ID: " + componentId); |
| } |
| 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) { |
| if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| try { |
| mUsageDurationsMs[CUSTOM_POWER_COMPONENT_OFFSET + componentId] = |
| componentUsageDurationMillis; |
| } catch (ArrayIndexOutOfBoundsException e) { |
| throw new IllegalArgumentException( |
| "Unsupported custom power component ID: " + componentId); |
| } |
| return this; |
| } |
| |
| public void addPowerAndDuration(PowerComponents.Builder other) { |
| addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs, |
| other.mPowerModels); |
| } |
| |
| public void addPowerAndDuration(PowerComponents other) { |
| addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs, |
| other.mPowerModels); |
| } |
| |
| private void addPowerAndDuration(double[] powerComponentsMah, |
| long[] usageDurationsMs, byte[] powerModels) { |
| if (mPowerComponentsMah.length != powerComponentsMah.length) { |
| throw new IllegalArgumentException( |
| "Number of power components does not match: " + powerComponentsMah.length |
| + ", expected: " + mPowerComponentsMah.length); |
| } |
| |
| for (int i = mPowerComponentsMah.length - 1; i >= 0; i--) { |
| mPowerComponentsMah[i] += powerComponentsMah[i]; |
| } |
| for (int i = mUsageDurationsMs.length - 1; i >= 0; i--) { |
| mUsageDurationsMs[i] += usageDurationsMs[i]; |
| } |
| if (mPowerModels != null && powerModels != null) { |
| for (int i = mPowerModels.length - 1; i >= 0; i--) { |
| if (mPowerModels[i] == POWER_MODEL_UNINITIALIZED) { |
| mPowerModels[i] = powerModels[i]; |
| } else if (mPowerModels[i] != powerModels[i] |
| && powerModels[i] != POWER_MODEL_UNINITIALIZED) { |
| mPowerModels[i] = BatteryConsumer.POWER_MODEL_UNDEFINED; |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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 i = mPowerComponentsMah.length - 1; i >= 0; i--) { |
| totalPowerMah += mPowerComponentsMah[i]; |
| } |
| return totalPowerMah; |
| } |
| |
| private byte[] getPowerModels() { |
| if (mPowerModels == null) { |
| return null; |
| } |
| |
| byte[] powerModels = new byte[mPowerModels.length]; |
| for (int i = mPowerModels.length - 1; i >= 0; i--) { |
| powerModels[i] = mPowerModels[i] != POWER_MODEL_UNINITIALIZED ? mPowerModels[i] |
| : BatteryConsumer.POWER_MODEL_UNDEFINED; |
| } |
| return powerModels; |
| } |
| |
| /** |
| * Creates a read-only object out of the Builder values. |
| */ |
| @NonNull |
| public PowerComponents build() { |
| return new PowerComponents(this); |
| } |
| } |
| } |