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