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