• 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 
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 measured 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 manually decisions about when to
45  * accumulate since it is trivial. However, in the future, a TimeBase could be used instead.
46  */
47 @VisibleForTesting
48 public class MeasuredEnergyStats {
49     private static final String TAG = "MeasuredEnergyStats";
50 
51     // Note: {@link com.android.internal.os.BatteryStatsImpl#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     })
77     @Retention(RetentionPolicy.SOURCE)
78     public @interface StandardPowerBucket {
79     }
80 
81     private static final int INVALID_STATE = -1;
82 
83     /**
84      * Configuration of measured energy stats: which power rails (buckets)  are supported on
85      * this device, what custom power drains are supported etc.
86      */
87     public static class Config {
88         private final boolean[] mSupportedStandardBuckets;
89         @NonNull
90         private final String[] mCustomBucketNames;
91         private final boolean[] mSupportedMultiStateBuckets;
92         @NonNull
93         private final String[] mStateNames;
94 
Config(@onNull boolean[] supportedStandardBuckets, @Nullable String[] customBucketNames, @NonNull int[] supportedMultiStateBuckets, @Nullable String[] stateNames)95         public Config(@NonNull boolean[] supportedStandardBuckets,
96                 @Nullable String[] customBucketNames,
97                 @NonNull int[] supportedMultiStateBuckets,
98                 @Nullable String[] stateNames) {
99             mSupportedStandardBuckets = supportedStandardBuckets;
100             mCustomBucketNames = customBucketNames != null ? customBucketNames : new String[0];
101             mSupportedMultiStateBuckets =
102                     new boolean[supportedStandardBuckets.length + mCustomBucketNames.length];
103             for (int bucket : supportedMultiStateBuckets) {
104                 if (mSupportedStandardBuckets[bucket]) {
105                     mSupportedMultiStateBuckets[bucket] = true;
106                 }
107             }
108             mStateNames = stateNames != null ? stateNames : new String[] {""};
109         }
110 
111         /**
112          * Returns true if the supplied Config is compatible with this one and therefore
113          * data collected with one of them will work with the other.
114          */
isCompatible(Config other)115         public boolean isCompatible(Config other) {
116             return Arrays.equals(mSupportedStandardBuckets, other.mSupportedStandardBuckets)
117                     && Arrays.equals(mCustomBucketNames, other.mCustomBucketNames)
118                     && Arrays.equals(mSupportedMultiStateBuckets,
119                     other.mSupportedMultiStateBuckets)
120                     && Arrays.equals(mStateNames, other.mStateNames);
121         }
122 
123         /**
124          * Writes the Config object into the supplied Parcel.
125          */
writeToParcel(@ullable Config config, Parcel out)126         public static void writeToParcel(@Nullable Config config, Parcel out) {
127             if (config == null) {
128                 out.writeBoolean(false);
129                 return;
130             }
131 
132             out.writeBoolean(true);
133             out.writeInt(config.mSupportedStandardBuckets.length);
134             out.writeBooleanArray(config.mSupportedStandardBuckets);
135             out.writeStringArray(config.mCustomBucketNames);
136             int multiStateBucketCount = 0;
137             for (boolean supported : config.mSupportedMultiStateBuckets) {
138                 if (supported) {
139                     multiStateBucketCount++;
140                 }
141             }
142             final int[] supportedMultiStateBuckets = new int[multiStateBucketCount];
143             int index = 0;
144             for (int bucket = 0; bucket < config.mSupportedMultiStateBuckets.length; bucket++) {
145                 if (config.mSupportedMultiStateBuckets[bucket]) {
146                     supportedMultiStateBuckets[index++] = bucket;
147                 }
148             }
149             out.writeInt(multiStateBucketCount);
150             out.writeIntArray(supportedMultiStateBuckets);
151             out.writeStringArray(config.mStateNames);
152         }
153 
154         /**
155          * Reads a Config object from the supplied Parcel.
156          */
157         @Nullable
createFromParcel(Parcel in)158         public static Config createFromParcel(Parcel in) {
159             if (!in.readBoolean()) {
160                 return null;
161             }
162 
163             final int supportedStandardBucketCount = in.readInt();
164             final boolean[] supportedStandardBuckets = new boolean[supportedStandardBucketCount];
165             in.readBooleanArray(supportedStandardBuckets);
166             final String[] customBucketNames = in.readStringArray();
167             final int supportedMultiStateBucketCount = in.readInt();
168             final int[] supportedMultiStateBuckets = new int[supportedMultiStateBucketCount];
169             in.readIntArray(supportedMultiStateBuckets);
170             final String[] stateNames = in.readStringArray();
171             return new Config(supportedStandardBuckets, customBucketNames,
172                     supportedMultiStateBuckets, stateNames);
173         }
174 
175         /** Get number of possible buckets, including both standard and custom ones. */
getNumberOfBuckets()176         private int getNumberOfBuckets() {
177             return mSupportedStandardBuckets.length + mCustomBucketNames.length;
178         }
179 
180         /**
181          * Returns true if the specified charge bucket is tracked.
182          */
isSupportedBucket(int index)183         public boolean isSupportedBucket(int index) {
184             return mSupportedStandardBuckets[index];
185         }
186 
187         @NonNull
getCustomBucketNames()188         public String[] getCustomBucketNames() {
189             return mCustomBucketNames;
190         }
191 
192         /**
193          * Returns true if the specified charge bucket is tracked on a per-state basis.
194          */
isSupportedMultiStateBucket(int index)195         public boolean isSupportedMultiStateBucket(int index) {
196             return mSupportedMultiStateBuckets[index];
197         }
198 
199         @NonNull
getStateNames()200         public String[] getStateNames() {
201             return mStateNames;
202         }
203 
204         /**
205          * If the index is a standard bucket, returns its name; otherwise returns its prefixed
206          * custom bucket number.
207          */
getBucketName(int index)208         private String getBucketName(int index) {
209             if (isValidStandardBucket(index)) {
210                 return DebugUtils.valueToString(MeasuredEnergyStats.class, "POWER_BUCKET_", index);
211             }
212             final int customBucket = indexToCustomBucket(index);
213             StringBuilder name = new StringBuilder().append("CUSTOM_").append(customBucket);
214             if (!TextUtils.isEmpty(mCustomBucketNames[customBucket])) {
215                 name.append('(').append(mCustomBucketNames[customBucket]).append(')');
216             }
217             return name.toString();
218         }
219     }
220 
221     private final Config mConfig;
222 
223     /**
224      * Total charge (in microcoulombs) that a power bucket (including both
225      * {@link StandardPowerBucket} and custom buckets) has accumulated since the last reset.
226      * Values MUST be non-zero or POWER_DATA_UNAVAILABLE. Accumulation only occurs
227      * while the necessary conditions are satisfied (e.g. on battery).
228      *
229      * Charge for both {@link StandardPowerBucket}s and custom power buckets are stored in this
230      * array, and may internally both referred to as 'buckets'. This is an implementation detail;
231      * externally, we differentiate between these two data sources.
232      *
233      * Warning: Long array is used for access speed. If the number of supported subsystems
234      * becomes large, consider using an alternate data structure such as a SparseLongArray.
235      */
236     private final long[] mAccumulatedChargeMicroCoulomb;
237 
238     private LongMultiStateCounter[] mAccumulatedMultiStateChargeMicroCoulomb;
239     private int mState = INVALID_STATE;
240     private long mStateChangeTimestampMs;
241 
242     /**
243      * Creates a MeasuredEnergyStats set to support the provided power buckets.
244      * supportedStandardBuckets must be of size {@link #NUMBER_STANDARD_POWER_BUCKETS}.
245      * numCustomBuckets >= 0 is the number of (non-standard) custom power buckets on the device.
246      */
MeasuredEnergyStats(MeasuredEnergyStats.Config config)247     public MeasuredEnergyStats(MeasuredEnergyStats.Config config) {
248         mConfig = config;
249         final int numTotalBuckets = config.getNumberOfBuckets();
250         mAccumulatedChargeMicroCoulomb = new long[numTotalBuckets];
251         // Initialize to all zeros where supported, otherwise POWER_DATA_UNAVAILABLE.
252         // All custom buckets are, by definition, supported, so their values stay at 0.
253         for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_POWER_BUCKETS; stdBucket++) {
254             if (!mConfig.mSupportedStandardBuckets[stdBucket]) {
255                 mAccumulatedChargeMicroCoulomb[stdBucket] = POWER_DATA_UNAVAILABLE;
256             }
257         }
258     }
259 
260     /**
261      * Reads a MeasuredEnergyStats from the supplied Parcel.
262      */
263     @Nullable
createFromParcel(Config config, Parcel in)264     public static MeasuredEnergyStats createFromParcel(Config config, Parcel in) {
265         if (!in.readBoolean()) {
266             return null;
267         }
268         return new MeasuredEnergyStats(config, in);
269     }
270 
271     /** Construct from parcel. */
MeasuredEnergyStats(MeasuredEnergyStats.Config config, Parcel in)272     public MeasuredEnergyStats(MeasuredEnergyStats.Config config, Parcel in) {
273         mConfig = config;
274 
275         final int size = in.readInt();
276         mAccumulatedChargeMicroCoulomb = new long[size];
277         in.readLongArray(mAccumulatedChargeMicroCoulomb);
278         if (in.readBoolean()) {
279             mAccumulatedMultiStateChargeMicroCoulomb = new LongMultiStateCounter[size];
280             for (int i = 0; i < size; i++) {
281                 if (in.readBoolean()) {
282                     mAccumulatedMultiStateChargeMicroCoulomb[i] =
283                             LongMultiStateCounter.CREATOR.createFromParcel(in);
284                 }
285             }
286         } else {
287             mAccumulatedMultiStateChargeMicroCoulomb = null;
288         }
289     }
290 
291     /** Write to parcel */
writeToParcel(Parcel out)292     public void writeToParcel(Parcel out) {
293         out.writeInt(mAccumulatedChargeMicroCoulomb.length);
294         out.writeLongArray(mAccumulatedChargeMicroCoulomb);
295         if (mAccumulatedMultiStateChargeMicroCoulomb != null) {
296             out.writeBoolean(true);
297             for (LongMultiStateCounter counter : mAccumulatedMultiStateChargeMicroCoulomb) {
298                 if (counter != null) {
299                     out.writeBoolean(true);
300                     counter.writeToParcel(out, 0);
301                 } else {
302                     out.writeBoolean(false);
303                 }
304             }
305         } else {
306             out.writeBoolean(false);
307         }
308     }
309 
310     /**
311      * Read from summary parcel.
312      * Note: Measured subsystem (and therefore bucket) availability may be different from when the
313      * summary parcel was written. Availability has already been correctly set in the constructor.
314      * Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} must be updated if summary
315      *       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(MeasuredEnergyStats, 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      */
createAndReadSummaryFromParcel( @ullable Config config, Parcel in)545     public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(
546             @Nullable Config config, Parcel in) {
547         final int arraySize = in.readInt();
548         // Check if any MeasuredEnergyStats exists on the parcel
549         if (arraySize == 0) return null;
550 
551         if (config == null) {
552             // Nothing supported anymore. Create placeholder object just to consume the parcel data.
553             final MeasuredEnergyStats mes = new MeasuredEnergyStats(
554                     new Config(new boolean[arraySize], null, new int[0], new String[]{""}));
555             mes.readSummaryFromParcel(in);
556             return null;
557         }
558 
559         if (arraySize != config.getNumberOfBuckets()) {
560             Slog.wtf(TAG, "Size of MeasuredEnergyStats parcel (" + arraySize
561                     + ") does not match config (" + config.getNumberOfBuckets() + ").");
562             // Something is horribly wrong. Just consume the parcel and return null.
563             final MeasuredEnergyStats mes = new MeasuredEnergyStats(config);
564             mes.readSummaryFromParcel(in);
565             return null;
566         }
567 
568         final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
569         stats.readSummaryFromParcel(in);
570         if (stats.containsInterestingData()) {
571             return stats;
572         } else {
573             // Don't waste RAM on it (and make sure not to persist it in the next writeSummary)
574             return null;
575         }
576     }
577 
578     /** Returns true iff any of the buckets are supported and non-zero. */
containsInterestingData()579     private boolean containsInterestingData() {
580         for (int index = 0; index < mAccumulatedChargeMicroCoulomb.length; index++) {
581             if (mAccumulatedChargeMicroCoulomb[index] > 0) return true;
582         }
583         return false;
584     }
585 
586     /**
587      * Write a MeasuredEnergyStats to a parcel. If the stats is null, just write a 0.
588      *
589      * Corresponding read performed by {@link #createAndReadSummaryFromParcel}.
590      */
writeSummaryToParcel(@ullable MeasuredEnergyStats stats, Parcel dest)591     public static void writeSummaryToParcel(@Nullable MeasuredEnergyStats stats, Parcel dest) {
592         if (stats == null) {
593             dest.writeInt(0);
594             return;
595         }
596         dest.writeInt(stats.mConfig.getNumberOfBuckets());
597         stats.writeSummaryToParcel(dest);
598     }
599 
600     /** Reset accumulated charges. */
reset()601     private void reset() {
602         final int numIndices = mConfig.getNumberOfBuckets();
603         for (int index = 0; index < numIndices; index++) {
604             setValueIfSupported(index, 0L);
605             if (mAccumulatedMultiStateChargeMicroCoulomb != null
606                     && mAccumulatedMultiStateChargeMicroCoulomb[index] != null) {
607                 mAccumulatedMultiStateChargeMicroCoulomb[index].reset();
608             }
609         }
610     }
611 
612     /** Reset accumulated charges of the given stats. */
resetIfNotNull(@ullable MeasuredEnergyStats stats)613     public static void resetIfNotNull(@Nullable MeasuredEnergyStats stats) {
614         if (stats != null) stats.reset();
615     }
616 
617     /** If the index is AVAILABLE, overwrite its value; otherwise leave it as UNAVAILABLE. */
setValueIfSupported(int index, long value)618     private void setValueIfSupported(int index, long value) {
619         if (mAccumulatedChargeMicroCoulomb[index] != POWER_DATA_UNAVAILABLE) {
620             mAccumulatedChargeMicroCoulomb[index] = value;
621         }
622     }
623 
624     /**
625      * Check if measuring the charge consumption of the given bucket is supported by this device.
626      * @throws IllegalArgumentException if not a valid {@link StandardPowerBucket}.
627      */
isStandardBucketSupported(@tandardPowerBucket int bucket)628     public boolean isStandardBucketSupported(@StandardPowerBucket int bucket) {
629         checkValidStandardBucket(bucket);
630         return isIndexSupported(bucket);
631     }
632 
isIndexSupported(int index)633     private boolean isIndexSupported(int index) {
634         return mAccumulatedChargeMicroCoulomb[index] != POWER_DATA_UNAVAILABLE;
635     }
636 
637     /** Dump debug data. */
dump(PrintWriter pw)638     public void dump(PrintWriter pw) {
639         pw.print("   ");
640         for (int index = 0; index < mAccumulatedChargeMicroCoulomb.length; index++) {
641             pw.print(mConfig.getBucketName(index));
642             pw.print(" : ");
643             pw.print(mAccumulatedChargeMicroCoulomb[index]);
644             if (!isIndexSupported(index)) {
645                 pw.print(" (unsupported)");
646             }
647             if (mAccumulatedMultiStateChargeMicroCoulomb != null) {
648                 final LongMultiStateCounter counter =
649                         mAccumulatedMultiStateChargeMicroCoulomb[index];
650                 if (counter != null) {
651                     pw.print(" [");
652                     for (int i = 0; i < mConfig.mStateNames.length; i++) {
653                         if (i != 0) {
654                             pw.print(" ");
655                         }
656                         pw.print(mConfig.mStateNames[i]);
657                         pw.print(": ");
658                         pw.print(counter.getCount(i));
659                     }
660                     pw.print("]");
661                 }
662             }
663             if (index != mAccumulatedChargeMicroCoulomb.length - 1) {
664                 pw.print(", ");
665             }
666         }
667         pw.println();
668     }
669 
670     /** Get the number of custom power buckets on this device. */
getNumberCustomPowerBuckets()671     public int getNumberCustomPowerBuckets() {
672         return mAccumulatedChargeMicroCoulomb.length - NUMBER_STANDARD_POWER_BUCKETS;
673     }
674 
customBucketToIndex(int customBucket)675     private static int customBucketToIndex(int customBucket) {
676         return customBucket + NUMBER_STANDARD_POWER_BUCKETS;
677     }
678 
indexToCustomBucket(int index)679     private static int indexToCustomBucket(int index) {
680         return index - NUMBER_STANDARD_POWER_BUCKETS;
681     }
682 
checkValidStandardBucket(@tandardPowerBucket int bucket)683     private static void checkValidStandardBucket(@StandardPowerBucket int bucket) {
684         if (!isValidStandardBucket(bucket)) {
685             throw new IllegalArgumentException("Illegal StandardPowerBucket " + bucket);
686         }
687     }
688 
isValidStandardBucket(@tandardPowerBucket int bucket)689     private static boolean isValidStandardBucket(@StandardPowerBucket int bucket) {
690         return bucket >= 0 && bucket < NUMBER_STANDARD_POWER_BUCKETS;
691     }
692 
693     /** Returns whether the given custom bucket is valid (exists) on this device. */
694     @VisibleForTesting
isValidCustomBucket(int customBucket)695     public boolean isValidCustomBucket(int customBucket) {
696         return customBucket >= 0
697                 && customBucketToIndex(customBucket) < mAccumulatedChargeMicroCoulomb.length;
698     }
699 }
700