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 
17 package com.android.internal.power;
18 
19 
20 import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
21 
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.os.Parcel;
26 import android.text.TextUtils;
27 import android.util.DebugUtils;
28 import android.util.Slog;
29 import android.view.Display;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.internal.os.LongMultiStateCounter;
33 
34 import java.io.PrintWriter;
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.Arrays;
38 
39 /**
40  * Tracks the charge consumption of various subsystems according to their
41  * {@link StandardPowerBucket} or custom power bucket (which is tied to
42  * {@link android.hardware.power.stats.EnergyConsumer.ordinal}).
43  *
44  * This class doesn't use a TimeBase, and instead requires manual decisions about when to
45  * accumulate since it is trivial. However, in the future, a TimeBase could be used instead.
46  */
47 @android.ravenwood.annotation.RavenwoodKeepWholeClass
48 public class EnergyConsumerStats {
49     private static final String TAG = "MeasuredEnergyStats";
50 
51     // Note: {@link BatteryStats#VERSION} MUST be updated if standard
52     // power bucket integers are modified/added/removed.
53     public static final int POWER_BUCKET_UNKNOWN = -1;
54     public static final int POWER_BUCKET_SCREEN_ON = 0;
55     public static final int POWER_BUCKET_SCREEN_DOZE = 1;
56     public static final int POWER_BUCKET_SCREEN_OTHER = 2;
57     public static final int POWER_BUCKET_CPU = 3;
58     public static final int POWER_BUCKET_WIFI = 4;
59     public static final int POWER_BUCKET_BLUETOOTH = 5;
60     public static final int POWER_BUCKET_GNSS = 6;
61     public static final int POWER_BUCKET_MOBILE_RADIO = 7;
62     public static final int POWER_BUCKET_CAMERA = 8;
63     public static final int POWER_BUCKET_PHONE = 9;
64     public static final int NUMBER_STANDARD_POWER_BUCKETS = 10; // Buckets above this are custom.
65 
66     @IntDef(prefix = {"POWER_BUCKET_"}, value = {
67             POWER_BUCKET_UNKNOWN,
68             POWER_BUCKET_SCREEN_ON,
69             POWER_BUCKET_SCREEN_DOZE,
70             POWER_BUCKET_SCREEN_OTHER,
71             POWER_BUCKET_CPU,
72             POWER_BUCKET_WIFI,
73             POWER_BUCKET_BLUETOOTH,
74             POWER_BUCKET_GNSS,
75             POWER_BUCKET_MOBILE_RADIO,
76             POWER_BUCKET_CAMERA,
77             POWER_BUCKET_PHONE,
78     })
79     @Retention(RetentionPolicy.SOURCE)
80     public @interface StandardPowerBucket {
81     }
82 
83     private static final int INVALID_STATE = -1;
84 
85     /**
86      * Configuration of measured energy stats: which power rails (buckets)  are supported on
87      * this device, what custom power drains are supported etc.
88      */
89     public static class Config {
90         private final boolean[] mSupportedStandardBuckets;
91         @NonNull
92         private final String[] mCustomBucketNames;
93         private final boolean[] mSupportedMultiStateBuckets;
94         @NonNull
95         private final String[] mStateNames;
96 
Config(@onNull boolean[] supportedStandardBuckets, @Nullable String[] customBucketNames, @NonNull int[] supportedMultiStateBuckets, @Nullable String[] stateNames)97         public Config(@NonNull boolean[] supportedStandardBuckets,
98                 @Nullable String[] customBucketNames,
99                 @NonNull int[] supportedMultiStateBuckets,
100                 @Nullable String[] stateNames) {
101             mSupportedStandardBuckets = supportedStandardBuckets;
102             mCustomBucketNames = customBucketNames != null ? customBucketNames : new String[0];
103             mSupportedMultiStateBuckets =
104                     new boolean[supportedStandardBuckets.length + mCustomBucketNames.length];
105             for (int bucket : supportedMultiStateBuckets) {
106                 if (mSupportedStandardBuckets[bucket]) {
107                     mSupportedMultiStateBuckets[bucket] = true;
108                 }
109             }
110             mStateNames = stateNames != null ? stateNames : new String[] {""};
111         }
112 
113         /**
114          * Returns true if the supplied Config is compatible with this one and therefore
115          * data collected with one of them will work with the other.
116          */
isCompatible(Config other)117         public boolean isCompatible(Config other) {
118             return Arrays.equals(mSupportedStandardBuckets, other.mSupportedStandardBuckets)
119                     && Arrays.equals(mCustomBucketNames, other.mCustomBucketNames)
120                     && Arrays.equals(mSupportedMultiStateBuckets,
121                     other.mSupportedMultiStateBuckets)
122                     && Arrays.equals(mStateNames, other.mStateNames);
123         }
124 
125         /**
126          * Writes the Config object into the supplied Parcel.
127          */
writeToParcel(@ullable Config config, Parcel out)128         public static void writeToParcel(@Nullable Config config, Parcel out) {
129             if (config == null) {
130                 out.writeBoolean(false);
131                 return;
132             }
133 
134             out.writeBoolean(true);
135             out.writeInt(config.mSupportedStandardBuckets.length);
136             out.writeBooleanArray(config.mSupportedStandardBuckets);
137             out.writeStringArray(config.mCustomBucketNames);
138             int multiStateBucketCount = 0;
139             for (boolean supported : config.mSupportedMultiStateBuckets) {
140                 if (supported) {
141                     multiStateBucketCount++;
142                 }
143             }
144             final int[] supportedMultiStateBuckets = new int[multiStateBucketCount];
145             int index = 0;
146             for (int bucket = 0; bucket < config.mSupportedMultiStateBuckets.length; bucket++) {
147                 if (config.mSupportedMultiStateBuckets[bucket]) {
148                     supportedMultiStateBuckets[index++] = bucket;
149                 }
150             }
151             out.writeInt(multiStateBucketCount);
152             out.writeIntArray(supportedMultiStateBuckets);
153             out.writeStringArray(config.mStateNames);
154         }
155 
156         /**
157          * Reads a Config object from the supplied Parcel.
158          */
159         @Nullable
createFromParcel(Parcel in)160         public static Config createFromParcel(Parcel in) {
161             if (!in.readBoolean()) {
162                 return null;
163             }
164 
165             final int supportedStandardBucketCount = in.readInt();
166             final boolean[] supportedStandardBuckets = new boolean[supportedStandardBucketCount];
167             in.readBooleanArray(supportedStandardBuckets);
168             final String[] customBucketNames = in.readStringArray();
169             final int supportedMultiStateBucketCount = in.readInt();
170             final int[] supportedMultiStateBuckets = new int[supportedMultiStateBucketCount];
171             in.readIntArray(supportedMultiStateBuckets);
172             final String[] stateNames = in.readStringArray();
173             return new Config(supportedStandardBuckets, customBucketNames,
174                     supportedMultiStateBuckets, stateNames);
175         }
176 
177         /** Get number of possible buckets, including both standard and custom ones. */
getNumberOfBuckets()178         private int getNumberOfBuckets() {
179             return mSupportedStandardBuckets.length + mCustomBucketNames.length;
180         }
181 
182         /**
183          * Returns true if the specified charge bucket is tracked.
184          */
isSupportedBucket(int index)185         public boolean isSupportedBucket(int index) {
186             return mSupportedStandardBuckets[index];
187         }
188 
189         @NonNull
getCustomBucketNames()190         public String[] getCustomBucketNames() {
191             return mCustomBucketNames;
192         }
193 
194         /**
195          * Returns true if the specified charge bucket is tracked on a per-state basis.
196          */
isSupportedMultiStateBucket(int index)197         public boolean isSupportedMultiStateBucket(int index) {
198             return mSupportedMultiStateBuckets[index];
199         }
200 
201         @NonNull
getStateNames()202         public String[] getStateNames() {
203             return mStateNames;
204         }
205 
206         /**
207          * If the index is a standard bucket, returns its name; otherwise returns its prefixed
208          * custom bucket number.
209          */
getBucketName(int index)210         private String getBucketName(int index) {
211             if (isValidStandardBucket(index)) {
212                 return DebugUtils.valueToString(EnergyConsumerStats.class, "POWER_BUCKET_", index);
213             }
214             final int customBucket = indexToCustomBucket(index);
215             StringBuilder name = new StringBuilder().append("CUSTOM_").append(customBucket);
216             if (!TextUtils.isEmpty(mCustomBucketNames[customBucket])) {
217                 name.append('(').append(mCustomBucketNames[customBucket]).append(')');
218             }
219             return name.toString();
220         }
221     }
222 
223     private final Config mConfig;
224 
225     /**
226      * Total charge (in microcoulombs) that a power bucket (including both
227      * {@link StandardPowerBucket} and custom buckets) has accumulated since the last reset.
228      * Values MUST be non-zero or POWER_DATA_UNAVAILABLE. Accumulation only occurs
229      * while the necessary conditions are satisfied (e.g. on battery).
230      *
231      * Charge for both {@link StandardPowerBucket}s and custom power buckets are stored in this
232      * array, and may internally both referred to as 'buckets'. This is an implementation detail;
233      * externally, we differentiate between these two data sources.
234      *
235      * Warning: Long array is used for access speed. If the number of supported subsystems
236      * becomes large, consider using an alternate data structure such as a SparseLongArray.
237      */
238     private final long[] mAccumulatedChargeMicroCoulomb;
239 
240     private LongMultiStateCounter[] mAccumulatedMultiStateChargeMicroCoulomb;
241     private int mState = INVALID_STATE;
242     private long mStateChangeTimestampMs;
243 
244     /**
245      * Creates a MeasuredEnergyStats set to support the provided power buckets.
246      * supportedStandardBuckets must be of size {@link #NUMBER_STANDARD_POWER_BUCKETS}.
247      * numCustomBuckets >= 0 is the number of (non-standard) custom power buckets on the device.
248      */
EnergyConsumerStats(EnergyConsumerStats.Config config)249     public EnergyConsumerStats(EnergyConsumerStats.Config config) {
250         mConfig = config;
251         final int numTotalBuckets = config.getNumberOfBuckets();
252         mAccumulatedChargeMicroCoulomb = new long[numTotalBuckets];
253         // Initialize to all zeros where supported, otherwise POWER_DATA_UNAVAILABLE.
254         // All custom buckets are, by definition, supported, so their values stay at 0.
255         for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_POWER_BUCKETS; stdBucket++) {
256             if (!mConfig.mSupportedStandardBuckets[stdBucket]) {
257                 mAccumulatedChargeMicroCoulomb[stdBucket] = POWER_DATA_UNAVAILABLE;
258             }
259         }
260     }
261 
262     /**
263      * Reads a MeasuredEnergyStats from the supplied Parcel.
264      */
265     @Nullable
createFromParcel(Config config, Parcel in)266     public static EnergyConsumerStats createFromParcel(Config config, Parcel in) {
267         if (!in.readBoolean()) {
268             return null;
269         }
270         return new EnergyConsumerStats(config, in);
271     }
272 
273     /** Construct from parcel. */
EnergyConsumerStats(EnergyConsumerStats.Config config, Parcel in)274     public EnergyConsumerStats(EnergyConsumerStats.Config config, Parcel in) {
275         mConfig = config;
276 
277         final int size = in.readInt();
278         mAccumulatedChargeMicroCoulomb = new long[size];
279         in.readLongArray(mAccumulatedChargeMicroCoulomb);
280         if (in.readBoolean()) {
281             mAccumulatedMultiStateChargeMicroCoulomb = new LongMultiStateCounter[size];
282             for (int i = 0; i < size; i++) {
283                 if (in.readBoolean()) {
284                     mAccumulatedMultiStateChargeMicroCoulomb[i] =
285                             LongMultiStateCounter.CREATOR.createFromParcel(in);
286                 }
287             }
288         } else {
289             mAccumulatedMultiStateChargeMicroCoulomb = null;
290         }
291     }
292 
293     /** Write to parcel */
writeToParcel(Parcel out)294     public void writeToParcel(Parcel out) {
295         out.writeInt(mAccumulatedChargeMicroCoulomb.length);
296         out.writeLongArray(mAccumulatedChargeMicroCoulomb);
297         if (mAccumulatedMultiStateChargeMicroCoulomb != null) {
298             out.writeBoolean(true);
299             for (LongMultiStateCounter counter : mAccumulatedMultiStateChargeMicroCoulomb) {
300                 if (counter != null) {
301                     out.writeBoolean(true);
302                     counter.writeToParcel(out, 0);
303                 } else {
304                     out.writeBoolean(false);
305                 }
306             }
307         } else {
308             out.writeBoolean(false);
309         }
310     }
311 
312     /**
313      * Read from summary parcel.
314      * Note: Measured subsystem (and therefore bucket) availability may be different from when the
315      * summary parcel was written. Availability has already been correctly set in the constructor.
316      * Note: {@link android.os.BatteryStats#VERSION} must be updated if summary parceling changes.
317      *
318      * Corresponding write performed by {@link #writeSummaryToParcel(Parcel)}.
319      */
readSummaryFromParcel(Parcel in)320     private void readSummaryFromParcel(Parcel in) {
321         final int numWrittenEntries = in.readInt();
322         for (int entry = 0; entry < numWrittenEntries; entry++) {
323             final int index = in.readInt();
324             final long chargeUC = in.readLong();
325             LongMultiStateCounter multiStateCounter = null;
326             if (in.readBoolean()) {
327                 multiStateCounter = LongMultiStateCounter.CREATOR.createFromParcel(in);
328                 if (mConfig == null
329                         || multiStateCounter.getStateCount() != mConfig.getStateNames().length) {
330                     multiStateCounter = null;
331                 }
332             }
333 
334             if (index < mAccumulatedChargeMicroCoulomb.length) {
335                 setValueIfSupported(index, chargeUC);
336                 if (multiStateCounter != null) {
337                     if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
338                         mAccumulatedMultiStateChargeMicroCoulomb =
339                                 new LongMultiStateCounter[mAccumulatedChargeMicroCoulomb.length];
340                     }
341                     mAccumulatedMultiStateChargeMicroCoulomb[index] = multiStateCounter;
342                 }
343             }
344         }
345     }
346 
347     /**
348      * Write to summary parcel.
349      * Note: Measured subsystem availability may be different when the summary parcel is read.
350      *
351      * Corresponding read performed by {@link #readSummaryFromParcel(Parcel)}.
352      */
writeSummaryToParcel(Parcel out)353     private void writeSummaryToParcel(Parcel out) {
354         final int posOfNumWrittenEntries = out.dataPosition();
355         out.writeInt(0);
356         int numWrittenEntries = 0;
357         // Write only the supported buckets (with non-zero charge, if applicable).
358         for (int index = 0; index < mAccumulatedChargeMicroCoulomb.length; index++) {
359             final long charge = mAccumulatedChargeMicroCoulomb[index];
360             if (charge <= 0) continue;
361 
362             out.writeInt(index);
363             out.writeLong(charge);
364             if (mAccumulatedMultiStateChargeMicroCoulomb != null
365                     && mAccumulatedMultiStateChargeMicroCoulomb[index] != null) {
366                 out.writeBoolean(true);
367                 mAccumulatedMultiStateChargeMicroCoulomb[index].writeToParcel(out, 0);
368             } else {
369                 out.writeBoolean(false);
370             }
371             numWrittenEntries++;
372         }
373         final int currPos = out.dataPosition();
374         out.setDataPosition(posOfNumWrittenEntries);
375         out.writeInt(numWrittenEntries);
376         out.setDataPosition(currPos);
377     }
378 
379     /** Updates the given standard power bucket with the given charge if accumulate is true. */
updateStandardBucket(@tandardPowerBucket int bucket, long chargeDeltaUC)380     public void updateStandardBucket(@StandardPowerBucket int bucket, long chargeDeltaUC) {
381         updateStandardBucket(bucket, chargeDeltaUC, 0);
382     }
383 
384     /**
385      * Updates the given standard power bucket with the given charge if supported.
386      * @param timestampMs elapsed realtime in milliseconds
387      */
updateStandardBucket(@tandardPowerBucket int bucket, long chargeDeltaUC, long timestampMs)388     public void updateStandardBucket(@StandardPowerBucket int bucket, long chargeDeltaUC,
389             long timestampMs) {
390         checkValidStandardBucket(bucket);
391         updateEntry(bucket, chargeDeltaUC, timestampMs);
392     }
393 
394     /** Updates the given custom power bucket with the given charge if accumulate is true. */
updateCustomBucket(int customBucket, long chargeDeltaUC)395     public void updateCustomBucket(int customBucket, long chargeDeltaUC) {
396         updateCustomBucket(customBucket, chargeDeltaUC, 0);
397     }
398 
399     /**
400      * Updates the given custom power bucket with the given charge if supported.
401      * @param timestampMs elapsed realtime in milliseconds
402      */
updateCustomBucket(int customBucket, long chargeDeltaUC, long timestampMs)403     public void updateCustomBucket(int customBucket, long chargeDeltaUC, long timestampMs) {
404         if (!isValidCustomBucket(customBucket)) {
405             Slog.e(TAG, "Attempted to update invalid custom bucket " + customBucket);
406             return;
407         }
408         final int index = customBucketToIndex(customBucket);
409         updateEntry(index, chargeDeltaUC, timestampMs);
410     }
411 
412     /** Updates the given bucket with the given charge delta. */
updateEntry(int index, long chargeDeltaUC, long timestampMs)413     private void updateEntry(int index, long chargeDeltaUC, long timestampMs) {
414         if (mAccumulatedChargeMicroCoulomb[index] >= 0L) {
415             mAccumulatedChargeMicroCoulomb[index] += chargeDeltaUC;
416             if (mState != INVALID_STATE && mConfig.isSupportedMultiStateBucket(index)) {
417                 if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
418                     mAccumulatedMultiStateChargeMicroCoulomb =
419                             new LongMultiStateCounter[mAccumulatedChargeMicroCoulomb.length];
420                 }
421                 LongMultiStateCounter counter =
422                         mAccumulatedMultiStateChargeMicroCoulomb[index];
423                 if (counter == null) {
424                     counter = new LongMultiStateCounter(mConfig.mStateNames.length);
425                     mAccumulatedMultiStateChargeMicroCoulomb[index] = counter;
426                     counter.setState(mState, mStateChangeTimestampMs);
427                     counter.updateValue(0, mStateChangeTimestampMs);
428                 }
429                 counter.updateValue(mAccumulatedChargeMicroCoulomb[index], timestampMs);
430             }
431         } else {
432             Slog.wtf(TAG, "Attempting to add " + chargeDeltaUC + " to unavailable bucket "
433                     + mConfig.getBucketName(index) + " whose value was "
434                     + mAccumulatedChargeMicroCoulomb[index]);
435         }
436     }
437 
438     /**
439      * Updates the "state" on all multi-state counters used by this MeasuredEnergyStats. Further
440      * accumulated charge updates will assign the deltas to this state, until the state changes.
441      *
442      * If setState is never called on a MeasuredEnergyStats object, then it does not track
443      * per-state usage.
444      */
setState(int state, long timestampMs)445     public void setState(int state, long timestampMs) {
446         mState = state;
447         mStateChangeTimestampMs = timestampMs;
448         if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
449             mAccumulatedMultiStateChargeMicroCoulomb =
450                     new LongMultiStateCounter[mAccumulatedChargeMicroCoulomb.length];
451         }
452         for (int i = 0; i < mAccumulatedMultiStateChargeMicroCoulomb.length; i++) {
453             LongMultiStateCounter counter = mAccumulatedMultiStateChargeMicroCoulomb[i];
454             if (counter == null && mConfig.isSupportedMultiStateBucket(i)) {
455                 counter = new LongMultiStateCounter(mConfig.mStateNames.length);
456                 counter.updateValue(0, timestampMs);
457                 mAccumulatedMultiStateChargeMicroCoulomb[i] = counter;
458             }
459             if (counter != null) {
460                 counter.setState(state, timestampMs);
461             }
462         }
463     }
464 
465     /**
466      * Return accumulated charge (in microcouloumb) for a standard power bucket since last reset.
467      * Returns {@link android.os.BatteryStats#POWER_DATA_UNAVAILABLE} if this data is unavailable.
468      * @throws IllegalArgumentException if no such {@link StandardPowerBucket}.
469      */
getAccumulatedStandardBucketCharge(@tandardPowerBucket int bucket)470     public long getAccumulatedStandardBucketCharge(@StandardPowerBucket int bucket) {
471         checkValidStandardBucket(bucket);
472         return mAccumulatedChargeMicroCoulomb[bucket];
473     }
474 
475     /**
476      * Returns the accumulated charge (in microcouloumb) for the standard power bucket and
477      * the specified state since last reset.
478      *
479      * Returns {@link android.os.BatteryStats#POWER_DATA_UNAVAILABLE} if this data is unavailable.
480      */
getAccumulatedStandardBucketCharge(@tandardPowerBucket int bucket, int state)481     public long getAccumulatedStandardBucketCharge(@StandardPowerBucket int bucket, int state) {
482         if (!mConfig.isSupportedMultiStateBucket(bucket)) {
483             return POWER_DATA_UNAVAILABLE;
484         }
485         if (mAccumulatedMultiStateChargeMicroCoulomb == null) {
486             return 0;
487         }
488         final LongMultiStateCounter counter = mAccumulatedMultiStateChargeMicroCoulomb[bucket];
489         if (counter == null) {
490             return 0;
491         }
492         return counter.getCount(state);
493     }
494 
495     /**
496      * Return accumulated charge (in microcoulomb) for the a custom power bucket since last
497      * reset.
498      * Returns {@link android.os.BatteryStats#POWER_DATA_UNAVAILABLE} if this data is unavailable.
499      */
500     @VisibleForTesting
getAccumulatedCustomBucketCharge(int customBucket)501     public long getAccumulatedCustomBucketCharge(int customBucket) {
502         if (!isValidCustomBucket(customBucket)) {
503             return POWER_DATA_UNAVAILABLE;
504         }
505         return mAccumulatedChargeMicroCoulomb[customBucketToIndex(customBucket)];
506     }
507 
508     /**
509      * Return accumulated charge (in microcoulomb) for all custom power buckets since last reset.
510      */
getAccumulatedCustomBucketCharges()511     public @NonNull long[] getAccumulatedCustomBucketCharges() {
512         final long[] charges = new long[getNumberCustomPowerBuckets()];
513         for (int bucket = 0; bucket < charges.length; bucket++) {
514             charges[bucket] = mAccumulatedChargeMicroCoulomb[customBucketToIndex(bucket)];
515         }
516         return charges;
517     }
518 
519     /**
520      * Map {@link android.view.Display} STATE_ to corresponding {@link StandardPowerBucket}.
521      */
getDisplayPowerBucket(int screenState)522     public static @StandardPowerBucket int getDisplayPowerBucket(int screenState) {
523         if (Display.isOnState(screenState)) {
524             return POWER_BUCKET_SCREEN_ON;
525         }
526         if (Display.isDozeState(screenState)) {
527             return POWER_BUCKET_SCREEN_DOZE;
528         }
529         return POWER_BUCKET_SCREEN_OTHER;
530     }
531 
532     /**
533      * Create a MeasuredEnergyStats using the template to determine which buckets are supported,
534      * and populate this new object from the given parcel.
535      *
536      * The parcel must be consistent with the template in terms of the number of
537      * possible (not necessarily supported) standard and custom buckets.
538      *
539      * Corresponding write performed by
540      * {@link #writeSummaryToParcel(EnergyConsumerStats, Parcel)}.
541      *
542      * @return a new MeasuredEnergyStats object as described.
543      *         Returns null if the stats contain no non-0 information (such as if template is null
544      *         or if the parcel indicates there is no data to populate).
545      */
546     @Nullable
createAndReadSummaryFromParcel(@ullable Config config, Parcel in)547     public static EnergyConsumerStats createAndReadSummaryFromParcel(@Nullable Config config,
548             Parcel in) {
549         final int arraySize = in.readInt();
550         // Check if any MeasuredEnergyStats exists on the parcel
551         if (arraySize == 0) return null;
552 
553         if (config == null) {
554             // Nothing supported anymore. Create placeholder object just to consume the parcel data.
555             final EnergyConsumerStats mes = new EnergyConsumerStats(
556                     new Config(new boolean[arraySize], null, new int[0], new String[]{""}));
557             mes.readSummaryFromParcel(in);
558             return null;
559         }
560 
561         if (arraySize != config.getNumberOfBuckets()) {
562             Slog.wtf(TAG, "Size of MeasuredEnergyStats parcel (" + arraySize
563                     + ") does not match config (" + config.getNumberOfBuckets() + ").");
564             // Something is horribly wrong. Just consume the parcel and return null.
565             final EnergyConsumerStats mes = new EnergyConsumerStats(config);
566             mes.readSummaryFromParcel(in);
567             return null;
568         }
569 
570         final EnergyConsumerStats stats = new EnergyConsumerStats(config);
571         stats.readSummaryFromParcel(in);
572         if (stats.containsInterestingData()) {
573             return stats;
574         } else {
575             // Don't waste RAM on it (and make sure not to persist it in the next writeSummary)
576             return null;
577         }
578     }
579 
580     /** Returns true iff any of the buckets are supported and non-zero. */
containsInterestingData()581     private boolean containsInterestingData() {
582         for (int index = 0; index < mAccumulatedChargeMicroCoulomb.length; index++) {
583             if (mAccumulatedChargeMicroCoulomb[index] > 0) return true;
584         }
585         return false;
586     }
587 
588     /**
589      * Write a MeasuredEnergyStats to a parcel. If the stats is null, just write a 0.
590      *
591      * Corresponding read performed by {@link #createAndReadSummaryFromParcel}.
592      */
writeSummaryToParcel(@ullable EnergyConsumerStats stats, Parcel dest)593     public static void writeSummaryToParcel(@Nullable EnergyConsumerStats stats, Parcel dest) {
594         if (stats == null) {
595             dest.writeInt(0);
596             return;
597         }
598         dest.writeInt(stats.mConfig.getNumberOfBuckets());
599         stats.writeSummaryToParcel(dest);
600     }
601 
602     /** Reset accumulated charges. */
reset()603     private void reset() {
604         final int numIndices = mConfig.getNumberOfBuckets();
605         for (int index = 0; index < numIndices; index++) {
606             setValueIfSupported(index, 0L);
607             if (mAccumulatedMultiStateChargeMicroCoulomb != null
608                     && mAccumulatedMultiStateChargeMicroCoulomb[index] != null) {
609                 mAccumulatedMultiStateChargeMicroCoulomb[index].reset();
610             }
611         }
612     }
613 
614     /** Reset accumulated charges of the given stats. */
resetIfNotNull(@ullable EnergyConsumerStats stats)615     public static void resetIfNotNull(@Nullable EnergyConsumerStats stats) {
616         if (stats != null) stats.reset();
617     }
618 
619     /** If the index is AVAILABLE, overwrite its value; otherwise leave it as UNAVAILABLE. */
setValueIfSupported(int index, long value)620     private void setValueIfSupported(int index, long value) {
621         if (mAccumulatedChargeMicroCoulomb[index] != POWER_DATA_UNAVAILABLE) {
622             mAccumulatedChargeMicroCoulomb[index] = value;
623         }
624     }
625 
626     /**
627      * Check if measuring the charge consumption of the given bucket is supported by this device.
628      * @throws IllegalArgumentException if not a valid {@link StandardPowerBucket}.
629      */
isStandardBucketSupported(@tandardPowerBucket int bucket)630     public boolean isStandardBucketSupported(@StandardPowerBucket int bucket) {
631         checkValidStandardBucket(bucket);
632         return isIndexSupported(bucket);
633     }
634 
isIndexSupported(int index)635     private boolean isIndexSupported(int index) {
636         return mAccumulatedChargeMicroCoulomb[index] != POWER_DATA_UNAVAILABLE;
637     }
638 
639     /** Dump debug data. */
dump(PrintWriter pw)640     public void dump(PrintWriter pw) {
641         pw.print("   ");
642         for (int index = 0; index < mAccumulatedChargeMicroCoulomb.length; index++) {
643             pw.print(mConfig.getBucketName(index));
644             pw.print(" : ");
645             pw.print(mAccumulatedChargeMicroCoulomb[index]);
646             if (!isIndexSupported(index)) {
647                 pw.print(" (unsupported)");
648             }
649             if (mAccumulatedMultiStateChargeMicroCoulomb != null) {
650                 final LongMultiStateCounter counter =
651                         mAccumulatedMultiStateChargeMicroCoulomb[index];
652                 if (counter != null) {
653                     pw.print(" [");
654                     for (int i = 0; i < mConfig.mStateNames.length; i++) {
655                         if (i != 0) {
656                             pw.print(" ");
657                         }
658                         pw.print(mConfig.mStateNames[i]);
659                         pw.print(": ");
660                         pw.print(counter.getCount(i));
661                     }
662                     pw.print("]");
663                 }
664             }
665             if (index != mAccumulatedChargeMicroCoulomb.length - 1) {
666                 pw.print(", ");
667             }
668         }
669         pw.println();
670     }
671 
672     /** Get the number of custom power buckets on this device. */
getNumberCustomPowerBuckets()673     public int getNumberCustomPowerBuckets() {
674         return mAccumulatedChargeMicroCoulomb.length - NUMBER_STANDARD_POWER_BUCKETS;
675     }
676 
customBucketToIndex(int customBucket)677     private static int customBucketToIndex(int customBucket) {
678         return customBucket + NUMBER_STANDARD_POWER_BUCKETS;
679     }
680 
indexToCustomBucket(int index)681     private static int indexToCustomBucket(int index) {
682         return index - NUMBER_STANDARD_POWER_BUCKETS;
683     }
684 
checkValidStandardBucket(@tandardPowerBucket int bucket)685     private static void checkValidStandardBucket(@StandardPowerBucket int bucket) {
686         if (!isValidStandardBucket(bucket)) {
687             throw new IllegalArgumentException("Illegal StandardPowerBucket " + bucket);
688         }
689     }
690 
isValidStandardBucket(@tandardPowerBucket int bucket)691     private static boolean isValidStandardBucket(@StandardPowerBucket int bucket) {
692         return bucket >= 0 && bucket < NUMBER_STANDARD_POWER_BUCKETS;
693     }
694 
695     /** Returns whether the given custom bucket is valid (exists) on this device. */
696     @VisibleForTesting
isValidCustomBucket(int customBucket)697     public boolean isValidCustomBucket(int customBucket) {
698         return customBucket >= 0
699                 && customBucketToIndex(customBucket) < mAccumulatedChargeMicroCoulomb.length;
700     }
701 }
702