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