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.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED;
19 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
20 import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
21 import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
22 import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.util.proto.ProtoOutputStream;
27 
28 import com.android.modules.utils.TypedXmlPullParser;
29 import com.android.modules.utils.TypedXmlSerializer;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
33 
34 import java.io.IOException;
35 import java.io.PrintWriter;
36 
37 /**
38  * Contains details of battery attribution data broken down to individual power drain types
39  * such as CPU, RAM, GPU etc.
40  *
41  * @hide
42  */
43 @android.ravenwood.annotation.RavenwoodKeepWholeClass
44 class PowerComponents {
45     private final BatteryConsumer.BatteryConsumerData mData;
46 
PowerComponents(@onNull Builder builder)47     PowerComponents(@NonNull Builder builder) {
48         mData = builder.mData;
49     }
50 
PowerComponents(BatteryConsumer.BatteryConsumerData data)51     PowerComponents(BatteryConsumer.BatteryConsumerData data) {
52         mData = data;
53     }
54 
55     /**
56      * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
57      */
getConsumedPower(@onNull BatteryConsumer.Dimensions dimensions)58     public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
59         if (dimensions.powerComponent != POWER_COMPONENT_ANY) {
60             return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent,
61                     dimensions.processState).mPowerColumnIndex);
62         } else if (dimensions.processState != PROCESS_STATE_ANY) {
63             if (!mData.layout.processStateDataIncluded) {
64                 throw new IllegalArgumentException(
65                         "No data included in BatteryUsageStats for " + dimensions);
66             }
67             final BatteryConsumer.Key[] keys =
68                     mData.layout.processStateKeys[dimensions.processState];
69             double totalPowerMah = 0;
70             for (int i = keys.length - 1; i >= 0; i--) {
71                 totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex);
72             }
73             return totalPowerMah;
74         } else {
75             return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
76         }
77     }
78 
79     /**
80      * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
81      *
82      * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
83      *            or {@link BatteryConsumer#getKeys} method.
84      * @return Amount of consumed power in mAh.
85      */
getConsumedPower(@onNull BatteryConsumer.Key key)86     public double getConsumedPower(@NonNull BatteryConsumer.Key key) {
87         return mData.getDouble(key.mPowerColumnIndex);
88     }
89 
90     /**
91      * Returns the amount of drain attributed to the specified custom drain type.
92      *
93      * @param componentId The ID of the custom power component.
94      * @return Amount of consumed power in mAh.
95      */
getConsumedPowerForCustomComponent(int componentId)96     public double getConsumedPowerForCustomComponent(int componentId) {
97         final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
98         if (index >= 0 && index < mData.layout.customPowerComponentCount) {
99             return mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + index);
100         } else {
101             throw new IllegalArgumentException(
102                     "Unsupported custom power component ID: " + componentId);
103         }
104     }
105 
getCustomPowerComponentName(int componentId)106     public String getCustomPowerComponentName(int componentId) {
107         final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
108         if (index >= 0 && index < mData.layout.customPowerComponentCount) {
109             try {
110                 return mData.layout.customPowerComponentNames[index];
111             } catch (ArrayIndexOutOfBoundsException e) {
112                 throw new IllegalArgumentException(
113                         "Unsupported custom power component ID: " + componentId);
114             }
115         } else {
116             throw new IllegalArgumentException(
117                     "Unsupported custom power component ID: " + componentId);
118         }
119     }
120 
121     @BatteryConsumer.PowerModel
getPowerModel(BatteryConsumer.Key key)122     int getPowerModel(BatteryConsumer.Key key) {
123         if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
124             throw new IllegalStateException(
125                     "Power model IDs were not requested in the BatteryUsageStatsQuery");
126         }
127         return mData.getInt(key.mPowerModelColumnIndex);
128     }
129 
130     /**
131      * Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
132      *
133      * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
134      *            or {@link BatteryConsumer#getKeys} method.
135      * @return Amount of time in milliseconds.
136      */
getUsageDurationMillis(BatteryConsumer.Key key)137     public long getUsageDurationMillis(BatteryConsumer.Key key) {
138         return mData.getLong(key.mDurationColumnIndex);
139     }
140 
141     /**
142      * Returns the amount of usage time attributed to the specified custom component.
143      *
144      * @param componentId The ID of the custom power component.
145      * @return Amount of time in milliseconds.
146      */
getUsageDurationForCustomComponentMillis(int componentId)147     public long getUsageDurationForCustomComponentMillis(int componentId) {
148         final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
149         if (index >= 0 && index < mData.layout.customPowerComponentCount) {
150             return mData.getLong(mData.layout.firstCustomUsageDurationColumn + index);
151         } else {
152             throw new IllegalArgumentException(
153                     "Unsupported custom power component ID: " + componentId);
154         }
155     }
156 
dump(PrintWriter pw, boolean skipEmptyComponents)157     public void dump(PrintWriter pw, boolean skipEmptyComponents) {
158         String separator = "";
159         StringBuilder sb = new StringBuilder();
160 
161         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
162                 componentId++) {
163             for (BatteryConsumer.Key key: mData.getKeys(componentId)) {
164                 final double componentPower = getConsumedPower(key);
165                 final long durationMs = getUsageDurationMillis(key);
166                 if (skipEmptyComponents && componentPower == 0 && durationMs == 0) {
167                     continue;
168                 }
169 
170                 sb.append(separator);
171                 separator = " ";
172                 sb.append(key.toShortString());
173                 sb.append("=");
174                 sb.append(BatteryStats.formatCharge(componentPower));
175 
176                 if (durationMs != 0) {
177                     sb.append(" (");
178                     BatteryStats.formatTimeMsNoSpace(sb, durationMs);
179                     sb.append(")");
180                 }
181             }
182         }
183 
184         final int customComponentCount = mData.layout.customPowerComponentCount;
185         for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
186                 customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
187                         + customComponentCount;
188                 customComponentId++) {
189             final double customComponentPower =
190                     getConsumedPowerForCustomComponent(customComponentId);
191             if (skipEmptyComponents && customComponentPower == 0) {
192                 continue;
193             }
194             sb.append(separator);
195             separator = " ";
196             sb.append(getCustomPowerComponentName(customComponentId));
197             sb.append("=");
198             sb.append(BatteryStats.formatCharge(customComponentPower));
199         }
200 
201         pw.print(sb);
202     }
203 
204     /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
hasStatsProtoData()205     boolean hasStatsProtoData() {
206         return writeStatsProtoImpl(null);
207     }
208 
209     /** Writes all atoms.proto POWER_COMPONENTS for this PowerComponents to the given proto. */
writeStatsProto(@onNull ProtoOutputStream proto)210     void writeStatsProto(@NonNull ProtoOutputStream proto) {
211         writeStatsProtoImpl(proto);
212     }
213 
214     /**
215      * Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto,
216      * and writes it to the given proto if it is non-null.
217      */
writeStatsProtoImpl(@ullable ProtoOutputStream proto)218     private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) {
219         boolean interestingData = false;
220 
221         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
222                 componentId++) {
223 
224             final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
225             for (BatteryConsumer.Key key : keys) {
226                 final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key));
227                 final long durationMs = getUsageDurationMillis(key);
228 
229                 if (powerDeciCoulombs == 0 && durationMs == 0) {
230                     // No interesting data. Make sure not to even write the COMPONENT int.
231                     continue;
232                 }
233 
234                 interestingData = true;
235                 if (proto == null) {
236                     // We're just asked whether there is data, not to actually write it.
237                     // And there is.
238                     return true;
239                 }
240 
241                 if (key.processState == PROCESS_STATE_ANY) {
242                     writePowerComponentUsage(proto,
243                             BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
244                             componentId, powerDeciCoulombs, durationMs);
245                 } else {
246                     writePowerUsageSlice(proto, componentId, powerDeciCoulombs, durationMs,
247                             key.processState);
248                 }
249             }
250         }
251         for (int idx = 0; idx < mData.layout.customPowerComponentCount; idx++) {
252             final int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + idx;
253             final long powerDeciCoulombs =
254                     convertMahToDeciCoulombs(getConsumedPowerForCustomComponent(componentId));
255             final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
256 
257             if (powerDeciCoulombs == 0 && durationMs == 0) {
258                 // No interesting data. Make sure not to even write the COMPONENT int.
259                 continue;
260             }
261 
262             interestingData = true;
263             if (proto == null) {
264                 // We're just asked whether there is data, not to actually write it. And there is.
265                 return true;
266             }
267 
268             writePowerComponentUsage(proto,
269                     BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
270                     componentId, powerDeciCoulombs, durationMs);
271         }
272         return interestingData;
273     }
274 
writePowerUsageSlice(ProtoOutputStream proto, int componentId, long powerDeciCoulombs, long durationMs, int processState)275     private void writePowerUsageSlice(ProtoOutputStream proto, int componentId,
276             long powerDeciCoulombs, long durationMs, int processState) {
277         final long slicesToken =
278                 proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.SLICES);
279         writePowerComponentUsage(proto,
280                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
281                         .POWER_COMPONENT,
282                 componentId, powerDeciCoulombs, durationMs);
283 
284         final int procState;
285         switch (processState) {
286             case BatteryConsumer.PROCESS_STATE_FOREGROUND:
287                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
288                         .FOREGROUND;
289                 break;
290             case BatteryConsumer.PROCESS_STATE_BACKGROUND:
291                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
292                         .BACKGROUND;
293                 break;
294             case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
295                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
296                         .FOREGROUND_SERVICE;
297                 break;
298             case BatteryConsumer.PROCESS_STATE_CACHED:
299                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
300                         .CACHED;
301                 break;
302             default:
303                 throw new IllegalArgumentException("Unknown process state: " + processState);
304         }
305 
306         proto.write(BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
307                 .PROCESS_STATE, procState);
308 
309         proto.end(slicesToken);
310     }
311 
writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId, long powerDeciCoulombs, long durationMs)312     private void writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId,
313             long powerDeciCoulombs, long durationMs) {
314         final long token = proto.start(tag);
315         proto.write(
316                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
317                         .COMPONENT,
318                 componentId);
319         proto.write(
320                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
321                         .POWER_DECI_COULOMBS,
322                 powerDeciCoulombs);
323         proto.write(
324                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
325                         .DURATION_MILLIS,
326                 durationMs);
327         proto.end(token);
328     }
329 
writeToXml(TypedXmlSerializer serializer)330     void writeToXml(TypedXmlSerializer serializer) throws IOException {
331         serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
332         for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
333                 componentId++) {
334             final BatteryConsumer.Key[] keys = mData.getKeys(componentId);
335             for (BatteryConsumer.Key key : keys) {
336                 final double powerMah = getConsumedPower(key);
337                 final long durationMs = getUsageDurationMillis(key);
338                 if (powerMah == 0 && durationMs == 0) {
339                     continue;
340                 }
341 
342                 serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
343                 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
344                 if (key.processState != PROCESS_STATE_UNSPECIFIED) {
345                     serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
346                             key.processState);
347                 }
348                 if (powerMah != 0) {
349                     serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
350                 }
351                 if (durationMs != 0) {
352                     serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
353                 }
354                 if (mData.layout.powerModelsIncluded) {
355                     serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
356                             getPowerModel(key));
357                 }
358                 serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
359             }
360         }
361 
362         final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
363                 + mData.layout.customPowerComponentCount;
364         for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
365                 componentId < customComponentEnd;
366                 componentId++) {
367             final double powerMah = getConsumedPowerForCustomComponent(componentId);
368             final long durationMs = getUsageDurationForCustomComponentMillis(componentId);
369             if (powerMah == 0 && durationMs == 0) {
370                 continue;
371             }
372 
373             serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
374             serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
375             if (powerMah != 0) {
376                 serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
377             }
378             if (durationMs != 0) {
379                 serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
380             }
381             serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT);
382         }
383 
384         serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
385     }
386 
387 
parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)388     static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)
389             throws XmlPullParserException, IOException {
390         int eventType = parser.getEventType();
391         if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(
392                 BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
393             throw new XmlPullParserException("Invalid XML parser state");
394         }
395 
396         while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
397                 BatteryUsageStats.XML_TAG_POWER_COMPONENTS))
398                 && eventType != XmlPullParser.END_DOCUMENT) {
399             if (eventType == XmlPullParser.START_TAG) {
400                 switch (parser.getName()) {
401                     case BatteryUsageStats.XML_TAG_COMPONENT: {
402                         int componentId = -1;
403                         int processState = PROCESS_STATE_UNSPECIFIED;
404                         double powerMah = 0;
405                         long durationMs = 0;
406                         int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
407                         for (int i = 0; i < parser.getAttributeCount(); i++) {
408                             switch (parser.getAttributeName(i)) {
409                                 case BatteryUsageStats.XML_ATTR_ID:
410                                     componentId = parser.getAttributeInt(i);
411                                     break;
412                                 case BatteryUsageStats.XML_ATTR_PROCESS_STATE:
413                                     processState = parser.getAttributeInt(i);
414                                     break;
415                                 case BatteryUsageStats.XML_ATTR_POWER:
416                                     powerMah = parser.getAttributeDouble(i);
417                                     break;
418                                 case BatteryUsageStats.XML_ATTR_DURATION:
419                                     durationMs = parser.getAttributeLong(i);
420                                     break;
421                                 case BatteryUsageStats.XML_ATTR_MODEL:
422                                     model = parser.getAttributeInt(i);
423                                     break;
424                             }
425                         }
426                         final BatteryConsumer.Key key =
427                                 builder.mData.getKey(componentId, processState);
428                         builder.setConsumedPower(key, powerMah, model);
429                         builder.setUsageDurationMillis(key, durationMs);
430                         break;
431                     }
432                     case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: {
433                         int componentId = -1;
434                         double powerMah = 0;
435                         long durationMs = 0;
436                         for (int i = 0; i < parser.getAttributeCount(); i++) {
437                             switch (parser.getAttributeName(i)) {
438                                 case BatteryUsageStats.XML_ATTR_ID:
439                                     componentId = parser.getAttributeInt(i);
440                                     break;
441                                 case BatteryUsageStats.XML_ATTR_POWER:
442                                     powerMah = parser.getAttributeDouble(i);
443                                     break;
444                                 case BatteryUsageStats.XML_ATTR_DURATION:
445                                     durationMs = parser.getAttributeLong(i);
446                                     break;
447                             }
448                         }
449                         builder.setConsumedPowerForCustomComponent(componentId, powerMah);
450                         builder.setUsageDurationForCustomComponentMillis(componentId, durationMs);
451                         break;
452                     }
453                 }
454             }
455             eventType = parser.next();
456         }
457     }
458 
459     /**
460      * Builder for PowerComponents.
461      */
462     static final class Builder {
463         private static final byte POWER_MODEL_UNINITIALIZED = -1;
464 
465         private final BatteryConsumer.BatteryConsumerData mData;
466         private final double mMinConsumedPowerThreshold;
467 
Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold)468         Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
469             mData = data;
470             mMinConsumedPowerThreshold = minConsumedPowerThreshold;
471             for (BatteryConsumer.Key[] keys : mData.layout.keys) {
472                 for (BatteryConsumer.Key key : keys) {
473                     if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
474                         mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
475                     }
476                 }
477             }
478         }
479 
480         @NonNull
setConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel)481         public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
482                 int powerModel) {
483             mData.putDouble(key.mPowerColumnIndex, componentPower);
484             if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
485                 mData.putInt(key.mPowerModelColumnIndex, powerModel);
486             }
487             return this;
488         }
489 
490         @NonNull
addConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel)491         public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower,
492                 int powerModel) {
493             mData.putDouble(key.mPowerColumnIndex,
494                     mData.getDouble(key.mPowerColumnIndex) + componentPower);
495             if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
496                 mData.putInt(key.mPowerModelColumnIndex, powerModel);
497             }
498             return this;
499         }
500 
501         /**
502          * Sets the amount of drain attributed to the specified custom drain type.
503          *
504          * @param componentId    The ID of the custom power component.
505          * @param componentPower Amount of consumed power in mAh.
506          */
507         @NonNull
setConsumedPowerForCustomComponent(int componentId, double componentPower)508         public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) {
509             final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
510             if (index < 0 || index >= mData.layout.customPowerComponentCount) {
511                 throw new IllegalArgumentException(
512                         "Unsupported custom power component ID: " + componentId);
513             }
514             mData.putDouble(mData.layout.firstCustomConsumedPowerColumn + index, componentPower);
515             return this;
516         }
517 
518         @NonNull
setUsageDurationMillis(BatteryConsumer.Key key, long componentUsageDurationMillis)519         public Builder setUsageDurationMillis(BatteryConsumer.Key key,
520                 long componentUsageDurationMillis) {
521             mData.putLong(key.mDurationColumnIndex, componentUsageDurationMillis);
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
setUsageDurationForCustomComponentMillis(int componentId, long componentUsageDurationMillis)532         public Builder setUsageDurationForCustomComponentMillis(int componentId,
533                 long componentUsageDurationMillis) {
534             final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
535             if (index < 0 || index >= mData.layout.customPowerComponentCount) {
536                 throw new IllegalArgumentException(
537                         "Unsupported custom power component ID: " + componentId);
538             }
539 
540             mData.putLong(mData.layout.firstCustomUsageDurationColumn + index,
541                     componentUsageDurationMillis);
542             return this;
543         }
544 
addPowerAndDuration(PowerComponents.Builder other)545         public void addPowerAndDuration(PowerComponents.Builder other) {
546             addPowerAndDuration(other.mData);
547         }
548 
addPowerAndDuration(PowerComponents other)549         public void addPowerAndDuration(PowerComponents other) {
550             addPowerAndDuration(other.mData);
551         }
552 
addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData)553         private void addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData) {
554             if (mData.layout.customPowerComponentCount
555                     != otherData.layout.customPowerComponentCount) {
556                 throw new IllegalArgumentException(
557                         "Number of custom power components does not match: "
558                                 + otherData.layout.customPowerComponentCount
559                                 + ", expected: " + mData.layout.customPowerComponentCount);
560             }
561 
562             for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0;
563                     componentId--) {
564                 final BatteryConsumer.Key[] keys = mData.layout.keys[componentId];
565                 for (BatteryConsumer.Key key: keys) {
566                     BatteryConsumer.Key otherKey = null;
567                     for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) {
568                         if (aKey.equals(key)) {
569                             otherKey = aKey;
570                             break;
571                         }
572                     }
573 
574                     if (otherKey == null) {
575                         continue;
576                     }
577 
578                     mData.putDouble(key.mPowerColumnIndex,
579                             mData.getDouble(key.mPowerColumnIndex)
580                                     + otherData.getDouble(otherKey.mPowerColumnIndex));
581                     mData.putLong(key.mDurationColumnIndex,
582                             mData.getLong(key.mDurationColumnIndex)
583                                     + otherData.getLong(otherKey.mDurationColumnIndex));
584 
585                     if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
586                         continue;
587                     }
588 
589                     boolean undefined = false;
590                     if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
591                         undefined = true;
592                     } else {
593                         final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
594                         int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
595                         if (powerModel == POWER_MODEL_UNINITIALIZED) {
596                             mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
597                         } else if (powerModel != otherPowerModel
598                                 && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
599                             undefined = true;
600                         }
601                     }
602 
603                     if (undefined) {
604                         mData.putInt(key.mPowerModelColumnIndex,
605                                 BatteryConsumer.POWER_MODEL_UNDEFINED);
606                     }
607                 }
608             }
609 
610             for (int i = mData.layout.customPowerComponentCount - 1; i >= 0; i--) {
611                 final int powerColumnIndex = mData.layout.firstCustomConsumedPowerColumn + i;
612                 final int otherPowerColumnIndex =
613                         otherData.layout.firstCustomConsumedPowerColumn + i;
614                 mData.putDouble(powerColumnIndex,
615                         mData.getDouble(powerColumnIndex) + otherData.getDouble(
616                                 otherPowerColumnIndex));
617 
618                 final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i;
619                 final int otherDurationColumnIndex =
620                         otherData.layout.firstCustomUsageDurationColumn + i;
621                 mData.putLong(usageColumnIndex,
622                         mData.getLong(usageColumnIndex) + otherData.getLong(
623                                 otherDurationColumnIndex)
624                 );
625             }
626         }
627 
628         /**
629          * Returns the total power accumulated by this builder so far. It may change
630          * by the time the {@code build()} method is called.
631          */
getTotalPower()632         public double getTotalPower() {
633             double totalPowerMah = 0;
634             for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
635                     componentId++) {
636                 totalPowerMah += mData.getDouble(
637                         mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex);
638             }
639             for (int i = 0; i < mData.layout.customPowerComponentCount; i++) {
640                 totalPowerMah += mData.getDouble(
641                         mData.layout.firstCustomConsumedPowerColumn + i);
642             }
643             return totalPowerMah;
644         }
645 
646         /**
647          * Creates a read-only object out of the Builder values.
648          */
649         @NonNull
build()650         public PowerComponents build() {
651             for (BatteryConsumer.Key[] keys : mData.layout.keys) {
652                 for (BatteryConsumer.Key key : keys) {
653                     if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
654                         if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
655                             mData.putInt(key.mPowerModelColumnIndex,
656                                     BatteryConsumer.POWER_MODEL_UNDEFINED);
657                         }
658                     }
659 
660                     if (mMinConsumedPowerThreshold != 0) {
661                         if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
662                             mData.putDouble(key.mPowerColumnIndex, 0);
663                         }
664                     }
665                 }
666             }
667 
668             if (mData.getDouble(mData.layout.totalConsumedPowerColumnIndex) == 0) {
669                 mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
670             }
671             return new PowerComponents(this);
672         }
673     }
674 }
675