• 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.POWER_COMPONENT_BASE;
20 import static android.os.BatteryConsumer.POWER_STATE_ANY;
21 import static android.os.BatteryConsumer.POWER_STATE_UNSPECIFIED;
22 import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
23 import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
24 import static android.os.BatteryConsumer.SCREEN_STATE_ANY;
25 import static android.os.BatteryConsumer.SCREEN_STATE_UNSPECIFIED;
26 import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.util.proto.ProtoOutputStream;
31 
32 import com.android.modules.utils.TypedXmlPullParser;
33 import com.android.modules.utils.TypedXmlSerializer;
34 
35 import org.xmlpull.v1.XmlPullParser;
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import java.io.IOException;
39 import java.io.PrintWriter;
40 
41 /**
42  * Contains details of battery attribution data broken down to individual power drain types
43  * such as CPU, RAM, GPU etc.
44  *
45  * @hide
46  */
47 @android.ravenwood.annotation.RavenwoodKeepWholeClass
48 class PowerComponents {
49     private final BatteryConsumer.BatteryConsumerData mData;
50 
PowerComponents(@onNull Builder builder)51     PowerComponents(@NonNull Builder builder) {
52         mData = builder.mData;
53     }
54 
PowerComponents(BatteryConsumer.BatteryConsumerData data)55     PowerComponents(BatteryConsumer.BatteryConsumerData data) {
56         mData = data;
57     }
58 
59     /**
60      * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
61      */
getConsumedPower(@onNull BatteryConsumer.Dimensions dimensions)62     public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) {
63         return getConsumedPower(dimensions.powerComponentId, dimensions.processState,
64                 dimensions.screenState, dimensions.powerState);
65     }
66 
67     /**
68      * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh.
69      */
getConsumedPower(@atteryConsumer.PowerComponentId int powerComponent, @BatteryConsumer.ProcessState int processState, @BatteryConsumer.ScreenState int screenState, @BatteryConsumer.PowerState int powerState)70     public double getConsumedPower(@BatteryConsumer.PowerComponentId int powerComponent,
71             @BatteryConsumer.ProcessState int processState,
72             @BatteryConsumer.ScreenState int screenState,
73             @BatteryConsumer.PowerState int powerState) {
74         if (powerComponent == POWER_COMPONENT_ANY && processState == PROCESS_STATE_ANY
75                 && screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) {
76             return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex);
77         }
78 
79         if (!mData.layout.processStateDataIncluded && !(processState == PROCESS_STATE_UNSPECIFIED
80                 || processState == PROCESS_STATE_ANY)) {
81             return 0;
82         }
83 
84         BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
85                 mData.layout.processStateDataIncluded && processState != PROCESS_STATE_ANY
86                         ? processState : PROCESS_STATE_UNSPECIFIED,
87                 mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY
88                         ? screenState : SCREEN_STATE_UNSPECIFIED,
89                 mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY
90                         ? powerState : POWER_STATE_UNSPECIFIED);
91         if (key != null && mData.hasValue(key.mPowerColumnIndex)) {
92             return mData.getDouble(key.mPowerColumnIndex);
93         }
94 
95         double total = 0;
96         for (BatteryConsumer.Key k : mData.layout.keys) {
97             if (k.matches(powerComponent, processState, screenState, powerState)) {
98                 total += mData.getDouble(k.mPowerColumnIndex);
99             }
100         }
101         return total;
102     }
103 
104     /**
105      * Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
106      */
getUsageDurationMillis(@onNull BatteryConsumer.Dimensions dimensions)107     public long getUsageDurationMillis(@NonNull BatteryConsumer.Dimensions dimensions) {
108         return getUsageDurationMillis(dimensions.powerComponentId, dimensions.processState,
109                 dimensions.screenState, dimensions.powerState);
110     }
111 
112     /**
113      * Total usage duration by this consumer, aggregated over the specified dimensions, in ms.
114      */
getUsageDurationMillis(@atteryConsumer.PowerComponentId int powerComponent, @BatteryConsumer.ProcessState int processState, @BatteryConsumer.ScreenState int screenState, @BatteryConsumer.PowerState int powerState)115     public long getUsageDurationMillis(@BatteryConsumer.PowerComponentId int powerComponent,
116             @BatteryConsumer.ProcessState int processState,
117             @BatteryConsumer.ScreenState int screenState,
118             @BatteryConsumer.PowerState int powerState) {
119         BatteryConsumer.Key key = mData.layout.getKey(powerComponent,
120                 mData.layout.processStateDataIncluded && processState != PROCESS_STATE_ANY
121                         ? processState : PROCESS_STATE_UNSPECIFIED,
122                 mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY
123                         ? screenState : SCREEN_STATE_UNSPECIFIED,
124                 mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY
125                         ? powerState : POWER_STATE_UNSPECIFIED);
126         if (key != null && mData.hasValue(key.mDurationColumnIndex)) {
127             return mData.getLong(key.mDurationColumnIndex);
128         }
129 
130         long total = 0;
131         for (BatteryConsumer.Key k : mData.layout.keys) {
132             if (k.matches(powerComponent, processState, screenState, powerState)) {
133                 total += mData.getLong(k.mDurationColumnIndex);
134             }
135         }
136         return total;
137     }
138 
139     /**
140      * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
141      *
142      * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
143      *            or {@link BatteryConsumer#getKeys} method.
144      * @return Amount of consumed power in mAh.
145      */
getConsumedPower(@onNull BatteryConsumer.Key key)146     public double getConsumedPower(@NonNull BatteryConsumer.Key key) {
147         if (mData.hasValue(key.mPowerColumnIndex)) {
148             return mData.getDouble(key.mPowerColumnIndex);
149         }
150         return getConsumedPower(key.powerComponentId, key.processState, key.screenState,
151                 key.powerState);
152     }
153 
getCustomPowerComponentName(int componentId)154     public String getCustomPowerComponentName(int componentId) {
155         return mData.layout.getPowerComponentName(componentId);
156     }
157 
158     /**
159      * Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
160      *
161      * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey}
162      *            or {@link BatteryConsumer#getKeys} method.
163      * @return Amount of time in milliseconds.
164      */
getUsageDurationMillis(BatteryConsumer.Key key)165     public long getUsageDurationMillis(BatteryConsumer.Key key) {
166         if (mData.hasValue(key.mDurationColumnIndex)) {
167             return mData.getLong(key.mDurationColumnIndex);
168         }
169 
170         return getUsageDurationMillis(key.powerComponentId, key.processState, key.screenState,
171                 key.powerState);
172     }
173 
dump(PrintWriter pw, @BatteryConsumer.ScreenState int screenState, @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents)174     void dump(PrintWriter pw, @BatteryConsumer.ScreenState int screenState,
175             @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
176         StringBuilder sb = new StringBuilder();
177         for (@BatteryConsumer.PowerComponentId int id : mData.layout.powerComponentIds) {
178             if (id == POWER_COMPONENT_BASE) {
179                 continue;
180             }
181 
182             dump(sb, id, PROCESS_STATE_ANY, screenState, powerState, skipEmptyComponents);
183             if (mData.layout.processStateDataIncluded) {
184                 for (int processState = 0; processState < BatteryConsumer.PROCESS_STATE_COUNT;
185                         processState++) {
186                     if (processState == PROCESS_STATE_UNSPECIFIED) {
187                         continue;
188                     }
189                     dump(sb, id, processState, screenState, powerState, skipEmptyComponents);
190                 }
191             }
192         }
193 
194         // Remove trailing spaces
195         while (!sb.isEmpty() && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
196             sb.setLength(sb.length() - 1);
197         }
198 
199         pw.println(sb);
200     }
201 
dump(StringBuilder sb, @BatteryConsumer.PowerComponentId int powerComponent, @BatteryConsumer.ProcessState int processState, @BatteryConsumer.ScreenState int screenState, @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents)202     private void dump(StringBuilder sb, @BatteryConsumer.PowerComponentId int powerComponent,
203             @BatteryConsumer.ProcessState int processState,
204             @BatteryConsumer.ScreenState int screenState,
205             @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) {
206         final double power = getConsumedPower(powerComponent, processState, screenState,
207                 powerState);
208         final long durationMs = getUsageDurationMillis(powerComponent, processState, screenState,
209                 powerState);
210         if (skipEmptyComponents && power == 0 && durationMs == 0) {
211             return;
212         }
213 
214         sb.append(mData.layout.getPowerComponentName(powerComponent));
215         if (processState != PROCESS_STATE_ANY) {
216             sb.append(':');
217             sb.append(BatteryConsumer.processStateToString(processState));
218         }
219         sb.append("=");
220         sb.append(BatteryStats.formatCharge(power));
221 
222         if (durationMs != 0) {
223             sb.append(" (");
224             BatteryStats.formatTimeMsNoSpace(sb, durationMs);
225             sb.append(")");
226         }
227         sb.append(' ');
228     }
229 
230     /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */
hasStatsProtoData()231     boolean hasStatsProtoData() {
232         return writeStatsProtoImpl(null);
233     }
234 
235     /** Writes all atoms.proto POWER_COMPONENTS for this PowerComponents to the given proto. */
writeStatsProto(@onNull ProtoOutputStream proto)236     void writeStatsProto(@NonNull ProtoOutputStream proto) {
237         writeStatsProtoImpl(proto);
238     }
239 
240     /**
241      * Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto,
242      * and writes it to the given proto if it is non-null.
243      */
writeStatsProtoImpl(@ullable ProtoOutputStream proto)244     private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) {
245         boolean interestingData = false;
246 
247         for (@BatteryConsumer.PowerComponentId int componentId : mData.layout.powerComponentIds) {
248             final BatteryConsumer.Key[] keys = mData.layout.getKeys(componentId);
249             for (BatteryConsumer.Key key : keys) {
250                 final long powerDeciCoulombs = convertMahToDeciCoulombs(
251                         getConsumedPower(key.powerComponentId, key.processState, key.screenState,
252                                 key.powerState));
253                 final long durationMs = getUsageDurationMillis(key.powerComponentId,
254                         key.processState, key.screenState, key.powerState);
255 
256                 if (powerDeciCoulombs == 0 && durationMs == 0) {
257                     // No interesting data. Make sure not to even write the COMPONENT int.
258                     continue;
259                 }
260 
261                 interestingData = true;
262                 if (proto == null) {
263                     // We're just asked whether there is data, not to actually write it.
264                     // And there is.
265                     return true;
266                 }
267 
268                 if (key.processState == PROCESS_STATE_UNSPECIFIED) {
269                     writePowerComponentUsage(proto,
270                             BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS,
271                             componentId, powerDeciCoulombs, durationMs);
272                 } else {
273                     writePowerUsageSlice(proto, componentId, powerDeciCoulombs, durationMs,
274                             key.processState);
275                 }
276             }
277         }
278         return interestingData;
279     }
280 
writePowerUsageSlice(ProtoOutputStream proto, int componentId, long powerDeciCoulombs, long durationMs, int processState)281     private void writePowerUsageSlice(ProtoOutputStream proto, int componentId,
282             long powerDeciCoulombs, long durationMs, int processState) {
283         final long slicesToken =
284                 proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.SLICES);
285         writePowerComponentUsage(proto,
286                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
287                         .POWER_COMPONENT,
288                 componentId, powerDeciCoulombs, durationMs);
289 
290         final int procState;
291         switch (processState) {
292             case BatteryConsumer.PROCESS_STATE_FOREGROUND:
293                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
294                         .FOREGROUND;
295                 break;
296             case BatteryConsumer.PROCESS_STATE_BACKGROUND:
297                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
298                         .BACKGROUND;
299                 break;
300             case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
301                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
302                         .FOREGROUND_SERVICE;
303                 break;
304             case BatteryConsumer.PROCESS_STATE_CACHED:
305                 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
306                         .CACHED;
307                 break;
308             default:
309                 throw new IllegalArgumentException("Unknown process state: " + processState);
310         }
311 
312         proto.write(BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice
313                 .PROCESS_STATE, procState);
314 
315         proto.end(slicesToken);
316     }
317 
writePowerComponentUsage(ProtoOutputStream proto, long tag, @BatteryConsumer.PowerComponentId int componentId, long powerDeciCoulombs, long durationMs)318     private void writePowerComponentUsage(ProtoOutputStream proto, long tag,
319             @BatteryConsumer.PowerComponentId int componentId, long powerDeciCoulombs,
320             long durationMs) {
321         final long token = proto.start(tag);
322         proto.write(
323                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
324                         .COMPONENT,
325                 componentId);
326         proto.write(
327                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
328                         .POWER_DECI_COULOMBS,
329                 powerDeciCoulombs);
330         proto.write(
331                 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage
332                         .DURATION_MILLIS,
333                 durationMs);
334         proto.end(token);
335     }
336 
writeToXml(TypedXmlSerializer serializer)337     void writeToXml(TypedXmlSerializer serializer) throws IOException {
338         serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
339         for (BatteryConsumer.Key key : mData.layout.keys) {
340             if (!mData.hasValue(key.mPowerColumnIndex)
341                     && !mData.hasValue(key.mDurationColumnIndex)) {
342                 continue;
343             }
344 
345             final double powerMah = getConsumedPower(key);
346             final long durationMs = getUsageDurationMillis(key);
347             if (powerMah == 0 && durationMs == 0) {
348                 continue;
349             }
350 
351             serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
352             serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, key.powerComponentId);
353             if (key.processState != PROCESS_STATE_UNSPECIFIED) {
354                 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
355                         key.processState);
356             }
357             if (key.screenState != SCREEN_STATE_UNSPECIFIED) {
358                 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCREEN_STATE,
359                         key.screenState);
360             }
361             if (key.powerState != POWER_STATE_UNSPECIFIED) {
362                 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_POWER_STATE,
363                         key.powerState);
364             }
365             if (powerMah != 0) {
366                 serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah);
367             }
368             if (durationMs != 0) {
369                 serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
370             }
371             serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
372         }
373         serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
374     }
375 
376     // No longer part of the BatteryUsageStats XML format. Preserved for compatibility
377     private static final String XML_TAG_CUSTOM_COMPONENT_COMPAT = "custom_component";
378 
parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)379     static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)
380             throws XmlPullParserException, IOException {
381         int eventType = parser.getEventType();
382         if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(
383                 BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) {
384             throw new XmlPullParserException("Invalid XML parser state");
385         }
386 
387         while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(
388                 BatteryUsageStats.XML_TAG_POWER_COMPONENTS))
389                 && eventType != XmlPullParser.END_DOCUMENT) {
390             if (eventType == XmlPullParser.START_TAG) {
391                 switch (parser.getName()) {
392                     case BatteryUsageStats.XML_TAG_COMPONENT:
393                     case XML_TAG_CUSTOM_COMPONENT_COMPAT: {
394                         int componentId = -1;
395                         int processState = PROCESS_STATE_UNSPECIFIED;
396                         int screenState = SCREEN_STATE_UNSPECIFIED;
397                         int powerState = POWER_STATE_UNSPECIFIED;
398                         double powerMah = 0;
399                         long durationMs = 0;
400                         for (int i = 0; i < parser.getAttributeCount(); i++) {
401                             switch (parser.getAttributeName(i)) {
402                                 case BatteryUsageStats.XML_ATTR_ID:
403                                     componentId = parser.getAttributeInt(i);
404                                     break;
405                                 case BatteryUsageStats.XML_ATTR_PROCESS_STATE:
406                                     processState = parser.getAttributeInt(i);
407                                     break;
408                                 case BatteryUsageStats.XML_ATTR_SCREEN_STATE:
409                                     screenState = parser.getAttributeInt(i);
410                                     break;
411                                 case BatteryUsageStats.XML_ATTR_POWER_STATE:
412                                     powerState = parser.getAttributeInt(i);
413                                     break;
414                                 case BatteryUsageStats.XML_ATTR_POWER:
415                                     powerMah = parser.getAttributeDouble(i);
416                                     break;
417                                 case BatteryUsageStats.XML_ATTR_DURATION:
418                                     durationMs = parser.getAttributeLong(i);
419                                     break;
420                             }
421                         }
422                         final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId,
423                                 processState, screenState, powerState);
424                         builder.addConsumedPower(key, powerMah);
425                         builder.addUsageDurationMillis(key, durationMs);
426                         break;
427                     }
428                 }
429             }
430             eventType = parser.next();
431         }
432     }
433 
434     /**
435      * Builder for PowerComponents.
436      */
437     static final class Builder {
438         private final BatteryConsumer.BatteryConsumerData mData;
439         private final double mMinConsumedPowerThreshold;
440 
Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold)441         Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
442             mData = data;
443             mMinConsumedPowerThreshold = minConsumedPowerThreshold;
444         }
445 
446         /**
447          * @deprecated use {@link #addConsumedPower(BatteryConsumer.Key, double)}
448          */
449         @Deprecated
450         @NonNull
setConsumedPower(BatteryConsumer.Key key, double componentPower)451         public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower) {
452             mData.putDouble(key.mPowerColumnIndex, componentPower);
453             return this;
454         }
455 
456         @NonNull
addConsumedPower(BatteryConsumer.Key key, double componentPower)457         public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower) {
458             mData.putDouble(key.mPowerColumnIndex,
459                     mData.getDouble(key.mPowerColumnIndex) + componentPower);
460             return this;
461         }
462 
463         /**
464          * @deprecated use {@link #addUsageDurationMillis(BatteryConsumer.Key, long)}
465          */
466         @Deprecated
467         @NonNull
setUsageDurationMillis(BatteryConsumer.Key key, long componentUsageDurationMillis)468         public Builder setUsageDurationMillis(BatteryConsumer.Key key,
469                 long componentUsageDurationMillis) {
470             mData.putLong(key.mDurationColumnIndex, componentUsageDurationMillis);
471             return this;
472         }
473 
474         @NonNull
addUsageDurationMillis(BatteryConsumer.Key key, long componentUsageDurationMillis)475         public Builder addUsageDurationMillis(BatteryConsumer.Key key,
476                 long componentUsageDurationMillis) {
477             mData.putLong(key.mDurationColumnIndex,
478                     mData.getLong(key.mDurationColumnIndex) + componentUsageDurationMillis);
479             return this;
480         }
481 
addPowerAndDuration(PowerComponents.Builder other)482         public void addPowerAndDuration(PowerComponents.Builder other) {
483             addPowerAndDuration(other.mData);
484         }
485 
addPowerAndDuration(PowerComponents other)486         public void addPowerAndDuration(PowerComponents other) {
487             addPowerAndDuration(other.mData);
488         }
489 
addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData)490         private void addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData) {
491             if (mData.layout.customPowerComponentCount
492                     != otherData.layout.customPowerComponentCount) {
493                 throw new IllegalArgumentException(
494                         "Number of custom power components does not match: "
495                                 + otherData.layout.customPowerComponentCount
496                                 + ", expected: " + mData.layout.customPowerComponentCount);
497             }
498 
499             for (BatteryConsumer.Key key : mData.layout.keys) {
500                 BatteryConsumer.Key otherKey = otherData.layout.getKey(key.powerComponentId,
501                         key.processState, key.screenState, key.powerState);
502                 if (otherKey == null) {
503                     continue;
504                 }
505                 if (mData.hasValue(key.mPowerColumnIndex)
506                         || otherData.hasValue(otherKey.mPowerColumnIndex)) {
507                     mData.putDouble(key.mPowerColumnIndex,
508                             mData.getDouble(key.mPowerColumnIndex)
509                                     + otherData.getDouble(otherKey.mPowerColumnIndex));
510                 }
511                 if (mData.hasValue(key.mDurationColumnIndex)
512                         || otherData.hasValue(otherKey.mDurationColumnIndex)) {
513                     mData.putLong(key.mDurationColumnIndex,
514                             mData.getLong(key.mDurationColumnIndex)
515                                     + otherData.getLong(otherKey.mDurationColumnIndex));
516                 }
517             }
518         }
519 
520         /**
521          * Returns the total power accumulated by this builder so far. It may change
522          * by the time the {@code build()} method is called.
523          */
getTotalPower()524         public double getTotalPower() {
525             double totalPowerMah = 0;
526             for (BatteryConsumer.Key key : mData.layout.keys) {
527                 if (key.processState == PROCESS_STATE_UNSPECIFIED
528                         && key.screenState == SCREEN_STATE_UNSPECIFIED
529                         && key.powerState == POWER_STATE_UNSPECIFIED) {
530                     totalPowerMah += mData.getDouble(key.mPowerColumnIndex);
531                 }
532             }
533             return totalPowerMah;
534         }
535 
536         /**
537          * Creates a read-only object out of the Builder values.
538          */
539         @NonNull
build()540         public PowerComponents build() {
541             for (BatteryConsumer.Key key : mData.layout.keys) {
542                 if (mMinConsumedPowerThreshold != 0) {
543                     if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
544                         mData.putDouble(key.mPowerColumnIndex, 0);
545                     }
546                 }
547             }
548 
549             mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower());
550             return new PowerComponents(this);
551         }
552     }
553 }
554