1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.os;
17 
18 import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.util.TypedXmlPullParser;
23 import android.util.TypedXmlSerializer;
24 import android.util.proto.ProtoOutputStream;
25 
26 import com.android.internal.os.PowerCalculator;
27 
28 import org.xmlpull.v1.XmlPullParser;
29 import org.xmlpull.v1.XmlPullParserException;
30 
31 import java.io.IOException;
32 import java.io.PrintWriter;
33 import java.util.Arrays;
34 
35 /**
36  * Contains details of battery attribution data broken down to individual power drain types
37  * such as CPU, RAM, GPU etc.
38  *
39  * @hide
40  */
41 class PowerComponents {
42     private static final int CUSTOM_POWER_COMPONENT_OFFSET = BatteryConsumer.POWER_COMPONENT_COUNT
43             - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
44 
45     private final double mConsumedPowerMah;
46     @NonNull
47     private final double[] mPowerComponentsMah;
48     @NonNull
49     private final long[] mUsageDurationsMs;
50     private final int mCustomPowerComponentCount;
51     @Nullable
52     private final byte[] mPowerModels;
53     // Not written to Parcel and must be explicitly restored during the parent object's unparceling
54     private String[] mCustomPowerComponentNames;
55 
PowerComponents(@onNull Builder builder)56     PowerComponents(@NonNull Builder builder) {
57         mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
58         mCustomPowerComponentCount = mCustomPowerComponentNames.length;
59         mPowerComponentsMah = builder.mPowerComponentsMah;
60         mUsageDurationsMs = builder.mUsageDurationsMs;
61         mConsumedPowerMah = builder.getTotalPower();
62         mPowerModels = builder.getPowerModels();
63     }
64 
PowerComponents(@onNull Parcel source)65     PowerComponents(@NonNull Parcel source) {
66         mConsumedPowerMah = source.readDouble();
67         mCustomPowerComponentCount = source.readInt();
68         mPowerComponentsMah = source.createDoubleArray();
69         mUsageDurationsMs = source.createLongArray();
70         if (source.readBoolean()) {
71             mPowerModels = new byte[BatteryConsumer.POWER_COMPONENT_COUNT];
72             source.readByteArray(mPowerModels);
73         } else {
74             mPowerModels = null;
75         }
76     }
77 
78     /** Writes contents to Parcel */
writeToParcel(@onNull Parcel dest, int flags)79     void writeToParcel(@NonNull Parcel dest, int flags) {
80         dest.writeDouble(mConsumedPowerMah);
81         dest.writeInt(mCustomPowerComponentCount);
82         dest.writeDoubleArray(mPowerComponentsMah);
83         dest.writeLongArray(mUsageDurationsMs);
84         if (mPowerModels != null) {
85             dest.writeBoolean(true);
86             dest.writeByteArray(mPowerModels);
87         } else {
88             dest.writeBoolean(false);
89         }
90     }
91 
92     /**
93      * Total power consumed by this consumer, in mAh.
94      */
getConsumedPower()95     public double getConsumedPower() {
96         return mConsumedPowerMah;
97     }
98 
99     /**
100      * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
101      *
102      * @param componentId The ID of the power component, e.g.
103      *                    {@link BatteryConsumer#POWER_COMPONENT_CPU}.
104      * @return Amount of consumed power in mAh.
105      */
getConsumedPower(@atteryConsumer.PowerComponent int componentId)106     public double getConsumedPower(@BatteryConsumer.PowerComponent int componentId) {
107         if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
108             throw new IllegalArgumentException(
109                     "Unsupported power component ID: " + componentId);
110         }
111         try {
112             return mPowerComponentsMah[componentId];
113         } catch (ArrayIndexOutOfBoundsException e) {
114             throw new IllegalArgumentException("Unsupported power component ID: " + componentId);
115         }
116     }
117 
118     /**
119      * Returns the amount of drain attributed to the specified custom drain type.
120      *
121      * @param componentId The ID of the custom power component.
122      * @return Amount of consumed power in mAh.
123      */
getConsumedPowerForCustomComponent(int componentId)124     public double getConsumedPowerForCustomComponent(int componentId) {
125         if (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
126                 && componentId < BatteryConsumer.LAST_CUSTOM_POWER_COMPONENT_ID) {
127             try {
128                 return mPowerComponentsMah[CUSTOM_POWER_COMPONENT_OFFSET + componentId];
129             } catch (ArrayIndexOutOfBoundsException e) {
130                 throw new IllegalArgumentException(
131                         "Unsupported custom power component ID: " + componentId);
132             }
133         } else {
134             throw new IllegalArgumentException(
135                     "Unsupported custom power component ID: " + componentId);
136         }
137     }
138 
setCustomPowerComponentNames(String[] customPowerComponentNames)139     void setCustomPowerComponentNames(String[] customPowerComponentNames) {
140         mCustomPowerComponentNames = customPowerComponentNames;
141     }
142 
getCustomPowerComponentName(int componentId)143     public String getCustomPowerComponentName(int componentId) {
144         if (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
145                 && componentId < BatteryConsumer.LAST_CUSTOM_POWER_COMPONENT_ID) {
146             try {
147                 return mCustomPowerComponentNames[componentId
148                         - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID];
149             } catch (ArrayIndexOutOfBoundsException e) {
150                 throw new IllegalArgumentException(
151                         "Unsupported custom power component ID: " + componentId);
152             }
153         } else {
154             throw new IllegalArgumentException(
155                     "Unsupported custom power component ID: " + componentId);
156         }
157     }
158 
hasPowerModels()159     public boolean hasPowerModels() {
160         return mPowerModels != null;
161     }
162 
163     @BatteryConsumer.PowerModel
getPowerModel(@atteryConsumer.PowerComponent int component)164     int getPowerModel(@BatteryConsumer.PowerComponent int component) {
165         if (!hasPowerModels()) {
166             throw new IllegalStateException(
167                     "Power model IDs were not requested in the BatteryUsageStatsQuery");
168         }
169         return mPowerModels[component];
170     }
171 
172     /**
173      * Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
174      *
175      * @param componentId The ID of the power component, e.g.
176      *                    {@link BatteryConsumer#POWER_COMPONENT_CPU}.
177      * @return Amount of time in milliseconds.
178      */
getUsageDurationMillis(@atteryConsumer.PowerComponent int componentId)179     public long getUsageDurationMillis(@BatteryConsumer.PowerComponent int componentId) {
180         if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
181             throw new IllegalArgumentException(
182                     "Unsupported power component ID: " + componentId);
183         }
184         try {
185             return mUsageDurationsMs[componentId];
186         } catch (ArrayIndexOutOfBoundsException e) {
187             throw new IllegalArgumentException("Unsupported power component ID: " + componentId);
188         }
189     }
190 
191     /**
192      * Returns the amount of usage time attributed to the specified custom component.
193      *
194      * @param componentId The ID of the custom power component.
195      * @return Amount of time in milliseconds.
196      */
getUsageDurationForCustomComponentMillis(int componentId)197     public long getUsageDurationForCustomComponentMillis(int componentId) {
198         if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
199             throw new IllegalArgumentException(
200                     "Unsupported custom power component ID: " + componentId);
201         }
202         try {
203             return mUsageDurationsMs[CUSTOM_POWER_COMPONENT_OFFSET + componentId];
204         } catch (ArrayIndexOutOfBoundsException e) {
205             throw new IllegalArgumentException(
206                     "Unsupported custom power component ID: " + componentId);
207         }
208     }
209 
getCustomPowerComponentCount()210     public int getCustomPowerComponentCount() {
211         return mCustomPowerComponentCount;
212     }
213 
214     /**
215      * Returns the largest usage duration among all power components.
216      */
getMaxComponentUsageDurationMillis()217     public long getMaxComponentUsageDurationMillis() {
218         long max = 0;
219         for (int i = mUsageDurationsMs.length - 1; i >= 0; i--) {
220             if (mUsageDurationsMs[i] > max) {
221                 max = mUsageDurationsMs[i];
222             }
223         }
224         return max;
225     }
226 
dump(PrintWriter pw, boolean skipEmptyComponents)227     public void dump(PrintWriter pw, boolean skipEmptyComponents) {
228         String separator = "";
229         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
230                 componentId++) {
231             final double componentPower = getConsumedPower(componentId);
232             if (skipEmptyComponents && componentPower == 0) {
233                 continue;
234             }
235             pw.print(separator); separator = " ";
236             pw.print(BatteryConsumer.powerComponentIdToString(componentId));
237             pw.print("=");
238             PowerCalculator.printPowerMah(pw, componentPower);
239         }
240 
241         final int customComponentCount = getCustomPowerComponentCount();
242         for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
243                 customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
244                         + customComponentCount;
245                 customComponentId++) {
246             final double customComponentPower =
247                     getConsumedPowerForCustomComponent(customComponentId);
248             if (skipEmptyComponents && customComponentPower == 0) {
249                 continue;
250             }
251             pw.print(separator); separator = " ";
252             pw.print(getCustomPowerComponentName(customComponentId));
253             pw.print("=");
254             PowerCalculator.printPowerMah(pw, customComponentPower);
255         }
256     }
257 
258     /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
hasStatsProtoData()259     boolean hasStatsProtoData() {
260         return writeStatsProtoImpl(null);
261     }
262 
263     /** Writes all atoms.proto POWER_COMPONENTS for this PowerComponents to the given proto. */
writeStatsProto(@onNull ProtoOutputStream proto)264     void writeStatsProto(@NonNull ProtoOutputStream proto) {
265         writeStatsProtoImpl(proto);
266     }
267 
268     /**
269      * Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto,
270      * and writes it to the given proto if it is non-null.
271      */
writeStatsProtoImpl(@ullable ProtoOutputStream proto)272     private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) {
273         boolean interestingData = false;
274 
275         for (int idx = 0; idx < mPowerComponentsMah.length; idx++) {
276             final int componentId = idx < BatteryConsumer.POWER_COMPONENT_COUNT ?
277                     idx : idx - CUSTOM_POWER_COMPONENT_OFFSET;
278             final long powerDeciCoulombs = convertMahToDeciCoulombs(mPowerComponentsMah[idx]);
279             final long durationMs = mUsageDurationsMs[idx];
280 
281             if (powerDeciCoulombs == 0 && durationMs == 0) {
282                 // No interesting data. Make sure not to even write the COMPONENT int.
283                 continue;
284             }
285 
286             interestingData = true;
287             if (proto == null) {
288                 // We're just asked whether there is data, not to actually write it. And there is.
289                 return true;
290             }
291 
292             final long token =
293                     proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS);
294             proto.write(
295                     BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
296                             .COMPONENT,
297                     componentId);
298             proto.write(
299                     BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
300                             .POWER_DECI_COULOMBS,
301                     powerDeciCoulombs);
302             proto.write(
303                     BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
304                             .DURATION_MILLIS,
305                     durationMs);
306             proto.end(token);
307         }
308         return interestingData;
309     }
310 
311     void writeToXml(TypedXmlSerializer serializer) throws IOException {
312         serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
313         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
314                 componentId++) {
315             final double powerMah = getConsumedPower(componentId);
316             final long durationMs = getUsageDurationMillis(componentId);
317             if (powerMah == 0 && durationMs == 0) {
318                 continue;
319             }
320 
321             serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
322             serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
323             if (powerMah != 0) {
324                 serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
325             }
326             if (durationMs != 0) {
327                 serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
328             }
329             if (mPowerModels != null) {
330                 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
331                         mPowerModels[componentId]);
332             }
333             serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
334         }
335 
336         final int customComponentEnd =
337                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + mCustomPowerComponentCount;
338         for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
339                 componentId < customComponentEnd;
340                 componentId++) {
341             final double powerMah = getConsumedPowerForCustomComponent(componentId);
342             final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
343             if (powerMah == 0 && durationMs == 0) {
344                 continue;
345             }
346 
347             serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
348             serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
349             if (powerMah != 0) {
350                 serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
351             }
352             if (durationMs != 0) {
353                 serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
354             }
355             serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
356         }
357 
358         serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
359     }
360 
361 
362     static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)
363             throws XmlPullParserException, IOException {
364         int eventType = parser.getEventType();
365         if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(
366                 BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
367             throw new XmlPullParserException("Invalid XML parser state");
368         }
369 
370         while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
371                 BatteryUsageStats.XML_TAG_POWER_COMPONENTS))
372                 && eventType != XmlPullParser.END_DOCUMENT) {
373             if (eventType == XmlPullParser.START_TAG) {
374                 switch (parser.getName()) {
375                     case BatteryUsageStats.XML_TAG_COMPONENT: {
376                         int componentId = -1;
377                         double powerMah = 0;
378                         long durationMs = 0;
379                         int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
380                         for (int i = 0; i < parser.getAttributeCount(); i++) {
381                             switch (parser.getAttributeName(i)) {
382                                 case BatteryUsageStats.XML_ATTR_ID:
383                                     componentId = parser.getAttributeInt(i);
384                                     break;
385                                 case BatteryUsageStats.XML_ATTR_POWER:
386                                     powerMah = parser.getAttributeDouble(i);
387                                     break;
388                                 case BatteryUsageStats.XML_ATTR_DURATION:
389                                     durationMs = parser.getAttributeLong(i);
390                                     break;
391                                 case BatteryUsageStats.XML_ATTR_MODEL:
392                                     model = parser.getAttributeInt(i);
393                                     break;
394                             }
395                         }
396                         builder.setConsumedPower(componentId, powerMah, model);
397                         builder.setUsageDurationMillis(componentId, durationMs);
398                         break;
399                     }
400                     case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: {
401                         int componentId = -1;
402                         double powerMah = 0;
403                         long durationMs = 0;
404                         for (int i = 0; i < parser.getAttributeCount(); i++) {
405                             switch (parser.getAttributeName(i)) {
406                                 case BatteryUsageStats.XML_ATTR_ID:
407                                     componentId = parser.getAttributeInt(i);
408                                     break;
409                                 case BatteryUsageStats.XML_ATTR_POWER:
410                                     powerMah = parser.getAttributeDouble(i);
411                                     break;
412                                 case BatteryUsageStats.XML_ATTR_DURATION:
413                                     durationMs = parser.getAttributeLong(i);
414                                     break;
415                             }
416                         }
417                         builder.setConsumedPowerForCustomComponent(componentId, powerMah);
418                         builder.setUsageDurationForCustomComponentMillis(componentId, durationMs);
419                         break;
420                     }
421                 }
422             }
423             eventType = parser.next();
424         }
425     }
426 
427     /**
428      * Builder for PowerComponents.
429      */
430     static final class Builder {
431         private static final byte POWER_MODEL_UNINITIALIZED = -1;
432 
433         private final double[] mPowerComponentsMah;
434         private final String[] mCustomPowerComponentNames;
435         private final long[] mUsageDurationsMs;
436         private final byte[] mPowerModels;
437 
438         Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels) {
439             mCustomPowerComponentNames = customPowerComponentNames;
440             int powerComponentCount =
441                     BatteryConsumer.POWER_COMPONENT_COUNT + mCustomPowerComponentNames.length;
442             mPowerComponentsMah = new double[powerComponentCount];
443             mUsageDurationsMs = new long[powerComponentCount];
444             if (includePowerModels) {
445                 mPowerModels = new byte[BatteryConsumer.POWER_COMPONENT_COUNT];
446                 Arrays.fill(mPowerModels, POWER_MODEL_UNINITIALIZED);
447             } else {
448                 mPowerModels = null;
449             }
450         }
451 
452         /**
453          * Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
454          *
455          * @param componentId    The ID of the power component, e.g.
456          *                       {@link BatteryConsumer#POWER_COMPONENT_CPU}.
457          * @param componentPower Amount of consumed power in mAh.
458          */
459         @NonNull
460         public Builder setConsumedPower(@BatteryConsumer.PowerComponent int componentId,
461                 double componentPower, @BatteryConsumer.PowerModel int powerModel) {
462             if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
463                 throw new IllegalArgumentException(
464                         "Unsupported power component ID: " + componentId);
465             }
466             try {
467                 mPowerComponentsMah[componentId] = componentPower;
468             } catch (ArrayIndexOutOfBoundsException e) {
469                 throw new IllegalArgumentException(
470                         "Unsupported power component ID: " + componentId);
471             }
472             if (mPowerModels != null) {
473                 mPowerModels[componentId] = (byte) powerModel;
474             }
475             return this;
476         }
477 
478         /**
479          * Sets the amount of drain attributed to the specified custom drain type.
480          *
481          * @param componentId    The ID of the custom power component.
482          * @param componentPower Amount of consumed power in mAh.
483          */
484         @NonNull
485         public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
486             if (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
487                     && componentId < BatteryConsumer.LAST_CUSTOM_POWER_COMPONENT_ID) {
488                 try {
489                     mPowerComponentsMah[CUSTOM_POWER_COMPONENT_OFFSET + componentId] =
490                             componentPower;
491                 } catch (ArrayIndexOutOfBoundsException e) {
492                     throw new IllegalArgumentException(
493                             "Unsupported custom power component ID: " + componentId);
494                 }
495             } else {
496                 throw new IllegalArgumentException(
497                         "Unsupported custom power component ID: " + componentId);
498             }
499             return this;
500         }
501 
502         /**
503          * Sets the amount of time used by the specified component, e.g. CPU, WiFi etc.
504          *
505          * @param componentId                  The ID of the power component, e.g.
506          *                                     {@link BatteryConsumer#POWER_COMPONENT_CPU}.
507          * @param componentUsageDurationMillis Amount of time in milliseconds.
508          */
509         @NonNull
510         public Builder setUsageDurationMillis(@BatteryConsumer.PowerComponent int componentId,
511                 long componentUsageDurationMillis) {
512             if (componentId >= BatteryConsumer.POWER_COMPONENT_COUNT) {
513                 throw new IllegalArgumentException(
514                         "Unsupported power component ID: " + componentId);
515             }
516             try {
517                 mUsageDurationsMs[componentId] = componentUsageDurationMillis;
518             } catch (ArrayIndexOutOfBoundsException e) {
519                 throw new IllegalArgumentException(
520                         "Unsupported power component ID: " + componentId);
521             }
522             return this;
523         }
524 
525         /**
526          * Sets the amount of time used by the specified custom component.
527          *
528          * @param componentId                  The ID of the custom power component.
529          * @param componentUsageDurationMillis Amount of time in milliseconds.
530          */
531         @NonNull
532         public Builder setUsageDurationForCustomComponentMillis(int componentId,
533                 long componentUsageDurationMillis) {
534             if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
535                 throw new IllegalArgumentException(
536                         "Unsupported custom power component ID: " + componentId);
537             }
538             try {
539                 mUsageDurationsMs[CUSTOM_POWER_COMPONENT_OFFSET + componentId] =
540                         componentUsageDurationMillis;
541             } catch (ArrayIndexOutOfBoundsException e) {
542                 throw new IllegalArgumentException(
543                         "Unsupported custom power component ID: " + componentId);
544             }
545             return this;
546         }
547 
548         public void addPowerAndDuration(PowerComponents.Builder other) {
549             addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs,
550                     other.mPowerModels);
551         }
552 
553         public void addPowerAndDuration(PowerComponents other) {
554             addPowerAndDuration(other.mPowerComponentsMah, other.mUsageDurationsMs,
555                     other.mPowerModels);
556         }
557 
558         private void addPowerAndDuration(double[] powerComponentsMah,
559                 long[] usageDurationsMs, byte[] powerModels) {
560             if (mPowerComponentsMah.length != powerComponentsMah.length) {
561                 throw new IllegalArgumentException(
562                         "Number of power components does not match: " + powerComponentsMah.length
563                                 + ", expected: " + mPowerComponentsMah.length);
564             }
565 
566             for (int i = mPowerComponentsMah.length - 1; i >= 0; i--) {
567                 mPowerComponentsMah[i] += powerComponentsMah[i];
568             }
569             for (int i = mUsageDurationsMs.length - 1; i >= 0; i--) {
570                 mUsageDurationsMs[i] += usageDurationsMs[i];
571             }
572             if (mPowerModels != null && powerModels != null) {
573                 for (int i = mPowerModels.length - 1; i >= 0; i--) {
574                     if (mPowerModels[i] == POWER_MODEL_UNINITIALIZED) {
575                         mPowerModels[i] = powerModels[i];
576                     } else if (mPowerModels[i] != powerModels[i]
577                             && powerModels[i] != POWER_MODEL_UNINITIALIZED) {
578                         mPowerModels[i] = BatteryConsumer.POWER_MODEL_UNDEFINED;
579                     }
580                 }
581             }
582         }
583 
584         /**
585          * Returns the total power accumulated by this builder so far. It may change
586          * by the time the {@code build()} method is called.
587          */
588         public double getTotalPower() {
589             double totalPowerMah = 0;
590             for (int i = mPowerComponentsMah.length - 1; i >= 0; i--) {
591                 totalPowerMah += mPowerComponentsMah[i];
592             }
593             return totalPowerMah;
594         }
595 
getPowerModels()596         private byte[] getPowerModels() {
597             if (mPowerModels == null) {
598                 return null;
599             }
600 
601             byte[] powerModels = new byte[mPowerModels.length];
602             for (int i = mPowerModels.length - 1; i >= 0; i--) {
603                 powerModels[i] = mPowerModels[i] != POWER_MODEL_UNINITIALIZED ? mPowerModels[i]
604                         : BatteryConsumer.POWER_MODEL_UNDEFINED;
605             }
606             return powerModels;
607         }
608 
609         /**
610          * Creates a read-only object out of the Builder values.
611          */
612         @NonNull
build()613         public PowerComponents build() {
614             return new PowerComponents(this);
615         }
616     }
617 }
618