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