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