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 package android.os; 17 18 import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED; 19 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY; 20 import static android.os.BatteryConsumer.PROCESS_STATE_ANY; 21 import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED; 22 import static android.os.BatteryConsumer.convertMahToDeciCoulombs; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.util.proto.ProtoOutputStream; 27 28 import com.android.modules.utils.TypedXmlPullParser; 29 import com.android.modules.utils.TypedXmlSerializer; 30 31 import org.xmlpull.v1.XmlPullParser; 32 import org.xmlpull.v1.XmlPullParserException; 33 34 import java.io.IOException; 35 import java.io.PrintWriter; 36 37 /** 38 * Contains details of battery attribution data broken down to individual power drain types 39 * such as CPU, RAM, GPU etc. 40 * 41 * @hide 42 */ 43 @android.ravenwood.annotation.RavenwoodKeepWholeClass 44 class PowerComponents { 45 private final BatteryConsumer.BatteryConsumerData mData; 46 PowerComponents(@onNull Builder builder)47 PowerComponents(@NonNull Builder builder) { 48 mData = builder.mData; 49 } 50 PowerComponents(BatteryConsumer.BatteryConsumerData data)51 PowerComponents(BatteryConsumer.BatteryConsumerData data) { 52 mData = data; 53 } 54 55 /** 56 * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh. 57 */ getConsumedPower(@onNull BatteryConsumer.Dimensions dimensions)58 public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) { 59 if (dimensions.powerComponent != POWER_COMPONENT_ANY) { 60 return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent, 61 dimensions.processState).mPowerColumnIndex); 62 } else if (dimensions.processState != PROCESS_STATE_ANY) { 63 if (!mData.layout.processStateDataIncluded) { 64 throw new IllegalArgumentException( 65 "No data included in BatteryUsageStats for " + dimensions); 66 } 67 final BatteryConsumer.Key[] keys = 68 mData.layout.processStateKeys[dimensions.processState]; 69 double totalPowerMah = 0; 70 for (int i = keys.length - 1; i >= 0; i--) { 71 totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex); 72 } 73 return totalPowerMah; 74 } else { 75 return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex); 76 } 77 } 78 79 /** 80 * Returns the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc. 81 * 82 * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey} 83 * or {@link BatteryConsumer#getKeys} method. 84 * @return Amount of consumed power in mAh. 85 */ getConsumedPower(@onNull BatteryConsumer.Key key)86 public double getConsumedPower(@NonNull BatteryConsumer.Key key) { 87 return mData.getDouble(key.mPowerColumnIndex); 88 } 89 90 /** 91 * Returns the amount of drain attributed to the specified custom drain type. 92 * 93 * @param componentId The ID of the custom power component. 94 * @return Amount of consumed power in mAh. 95 */ getConsumedPowerForCustomComponent(int componentId)96 public double getConsumedPowerForCustomComponent(int componentId) { 97 final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; 98 if (index >= 0 && index < mData.layout.customPowerComponentCount) { 99 return mData.getDouble(mData.layout.firstCustomConsumedPowerColumn + index); 100 } else { 101 throw new IllegalArgumentException( 102 "Unsupported custom power component ID: " + componentId); 103 } 104 } 105 getCustomPowerComponentName(int componentId)106 public String getCustomPowerComponentName(int componentId) { 107 final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; 108 if (index >= 0 && index < mData.layout.customPowerComponentCount) { 109 try { 110 return mData.layout.customPowerComponentNames[index]; 111 } catch (ArrayIndexOutOfBoundsException e) { 112 throw new IllegalArgumentException( 113 "Unsupported custom power component ID: " + componentId); 114 } 115 } else { 116 throw new IllegalArgumentException( 117 "Unsupported custom power component ID: " + componentId); 118 } 119 } 120 121 @BatteryConsumer.PowerModel getPowerModel(BatteryConsumer.Key key)122 int getPowerModel(BatteryConsumer.Key key) { 123 if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { 124 throw new IllegalStateException( 125 "Power model IDs were not requested in the BatteryUsageStatsQuery"); 126 } 127 return mData.getInt(key.mPowerModelColumnIndex); 128 } 129 130 /** 131 * Returns the amount of time used by the specified component, e.g. CPU, WiFi etc. 132 * 133 * @param key The key of the power component, obtained by calling {@link BatteryConsumer#getKey} 134 * or {@link BatteryConsumer#getKeys} method. 135 * @return Amount of time in milliseconds. 136 */ getUsageDurationMillis(BatteryConsumer.Key key)137 public long getUsageDurationMillis(BatteryConsumer.Key key) { 138 return mData.getLong(key.mDurationColumnIndex); 139 } 140 141 /** 142 * Returns the amount of usage time attributed to the specified custom component. 143 * 144 * @param componentId The ID of the custom power component. 145 * @return Amount of time in milliseconds. 146 */ getUsageDurationForCustomComponentMillis(int componentId)147 public long getUsageDurationForCustomComponentMillis(int componentId) { 148 final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; 149 if (index >= 0 && index < mData.layout.customPowerComponentCount) { 150 return mData.getLong(mData.layout.firstCustomUsageDurationColumn + index); 151 } else { 152 throw new IllegalArgumentException( 153 "Unsupported custom power component ID: " + componentId); 154 } 155 } 156 dump(PrintWriter pw, boolean skipEmptyComponents)157 public void dump(PrintWriter pw, boolean skipEmptyComponents) { 158 String separator = ""; 159 StringBuilder sb = new StringBuilder(); 160 161 for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; 162 componentId++) { 163 for (BatteryConsumer.Key key: mData.getKeys(componentId)) { 164 final double componentPower = getConsumedPower(key); 165 final long durationMs = getUsageDurationMillis(key); 166 if (skipEmptyComponents && componentPower == 0 && durationMs == 0) { 167 continue; 168 } 169 170 sb.append(separator); 171 separator = " "; 172 sb.append(key.toShortString()); 173 sb.append("="); 174 sb.append(BatteryStats.formatCharge(componentPower)); 175 176 if (durationMs != 0) { 177 sb.append(" ("); 178 BatteryStats.formatTimeMsNoSpace(sb, durationMs); 179 sb.append(")"); 180 } 181 } 182 } 183 184 final int customComponentCount = mData.layout.customPowerComponentCount; 185 for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; 186 customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID 187 + customComponentCount; 188 customComponentId++) { 189 final double customComponentPower = 190 getConsumedPowerForCustomComponent(customComponentId); 191 if (skipEmptyComponents && customComponentPower == 0) { 192 continue; 193 } 194 sb.append(separator); 195 separator = " "; 196 sb.append(getCustomPowerComponentName(customComponentId)); 197 sb.append("="); 198 sb.append(BatteryStats.formatCharge(customComponentPower)); 199 } 200 201 pw.print(sb); 202 } 203 204 /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */ hasStatsProtoData()205 boolean hasStatsProtoData() { 206 return writeStatsProtoImpl(null); 207 } 208 209 /** Writes all atoms.proto POWER_COMPONENTS for this PowerComponents to the given proto. */ writeStatsProto(@onNull ProtoOutputStream proto)210 void writeStatsProto(@NonNull ProtoOutputStream proto) { 211 writeStatsProtoImpl(proto); 212 } 213 214 /** 215 * Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto, 216 * and writes it to the given proto if it is non-null. 217 */ writeStatsProtoImpl(@ullable ProtoOutputStream proto)218 private boolean writeStatsProtoImpl(@Nullable ProtoOutputStream proto) { 219 boolean interestingData = false; 220 221 for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; 222 componentId++) { 223 224 final BatteryConsumer.Key[] keys = mData.getKeys(componentId); 225 for (BatteryConsumer.Key key : keys) { 226 final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key)); 227 final long durationMs = getUsageDurationMillis(key); 228 229 if (powerDeciCoulombs == 0 && durationMs == 0) { 230 // No interesting data. Make sure not to even write the COMPONENT int. 231 continue; 232 } 233 234 interestingData = true; 235 if (proto == null) { 236 // We're just asked whether there is data, not to actually write it. 237 // And there is. 238 return true; 239 } 240 241 if (key.processState == PROCESS_STATE_ANY) { 242 writePowerComponentUsage(proto, 243 BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS, 244 componentId, powerDeciCoulombs, durationMs); 245 } else { 246 writePowerUsageSlice(proto, componentId, powerDeciCoulombs, durationMs, 247 key.processState); 248 } 249 } 250 } 251 for (int idx = 0; idx < mData.layout.customPowerComponentCount; idx++) { 252 final int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + idx; 253 final long powerDeciCoulombs = 254 convertMahToDeciCoulombs(getConsumedPowerForCustomComponent(componentId)); 255 final long durationMs = getUsageDurationForCustomComponentMillis(componentId); 256 257 if (powerDeciCoulombs == 0 && durationMs == 0) { 258 // No interesting data. Make sure not to even write the COMPONENT int. 259 continue; 260 } 261 262 interestingData = true; 263 if (proto == null) { 264 // We're just asked whether there is data, not to actually write it. And there is. 265 return true; 266 } 267 268 writePowerComponentUsage(proto, 269 BatteryUsageStatsAtomsProto.BatteryConsumerData.POWER_COMPONENTS, 270 componentId, powerDeciCoulombs, durationMs); 271 } 272 return interestingData; 273 } 274 writePowerUsageSlice(ProtoOutputStream proto, int componentId, long powerDeciCoulombs, long durationMs, int processState)275 private void writePowerUsageSlice(ProtoOutputStream proto, int componentId, 276 long powerDeciCoulombs, long durationMs, int processState) { 277 final long slicesToken = 278 proto.start(BatteryUsageStatsAtomsProto.BatteryConsumerData.SLICES); 279 writePowerComponentUsage(proto, 280 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice 281 .POWER_COMPONENT, 282 componentId, powerDeciCoulombs, durationMs); 283 284 final int procState; 285 switch (processState) { 286 case BatteryConsumer.PROCESS_STATE_FOREGROUND: 287 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice 288 .FOREGROUND; 289 break; 290 case BatteryConsumer.PROCESS_STATE_BACKGROUND: 291 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice 292 .BACKGROUND; 293 break; 294 case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE: 295 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice 296 .FOREGROUND_SERVICE; 297 break; 298 case BatteryConsumer.PROCESS_STATE_CACHED: 299 procState = BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice 300 .CACHED; 301 break; 302 default: 303 throw new IllegalArgumentException("Unknown process state: " + processState); 304 } 305 306 proto.write(BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsageSlice 307 .PROCESS_STATE, procState); 308 309 proto.end(slicesToken); 310 } 311 writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId, long powerDeciCoulombs, long durationMs)312 private void writePowerComponentUsage(ProtoOutputStream proto, long tag, int componentId, 313 long powerDeciCoulombs, long durationMs) { 314 final long token = proto.start(tag); 315 proto.write( 316 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage 317 .COMPONENT, 318 componentId); 319 proto.write( 320 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage 321 .POWER_DECI_COULOMBS, 322 powerDeciCoulombs); 323 proto.write( 324 BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage 325 .DURATION_MILLIS, 326 durationMs); 327 proto.end(token); 328 } 329 writeToXml(TypedXmlSerializer serializer)330 void writeToXml(TypedXmlSerializer serializer) throws IOException { 331 serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS); 332 for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; 333 componentId++) { 334 final BatteryConsumer.Key[] keys = mData.getKeys(componentId); 335 for (BatteryConsumer.Key key : keys) { 336 final double powerMah = getConsumedPower(key); 337 final long durationMs = getUsageDurationMillis(key); 338 if (powerMah == 0 && durationMs == 0) { 339 continue; 340 } 341 342 serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT); 343 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId); 344 if (key.processState != PROCESS_STATE_UNSPECIFIED) { 345 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE, 346 key.processState); 347 } 348 if (powerMah != 0) { 349 serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); 350 } 351 if (durationMs != 0) { 352 serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); 353 } 354 if (mData.layout.powerModelsIncluded) { 355 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL, 356 getPowerModel(key)); 357 } 358 serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT); 359 } 360 } 361 362 final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID 363 + mData.layout.customPowerComponentCount; 364 for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; 365 componentId < customComponentEnd; 366 componentId++) { 367 final double powerMah = getConsumedPowerForCustomComponent(componentId); 368 final long durationMs = getUsageDurationForCustomComponentMillis(componentId); 369 if (powerMah == 0 && durationMs == 0) { 370 continue; 371 } 372 373 serializer.startTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT); 374 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId); 375 if (powerMah != 0) { 376 serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); 377 } 378 if (durationMs != 0) { 379 serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); 380 } 381 serializer.endTag(null, BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT); 382 } 383 384 serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS); 385 } 386 387 parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder)388 static void parseXml(TypedXmlPullParser parser, PowerComponents.Builder builder) 389 throws XmlPullParserException, IOException { 390 int eventType = parser.getEventType(); 391 if (eventType != XmlPullParser.START_TAG || !parser.getName().equals( 392 BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { 393 throw new XmlPullParserException("Invalid XML parser state"); 394 } 395 396 while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals( 397 BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) 398 && eventType != XmlPullParser.END_DOCUMENT) { 399 if (eventType == XmlPullParser.START_TAG) { 400 switch (parser.getName()) { 401 case BatteryUsageStats.XML_TAG_COMPONENT: { 402 int componentId = -1; 403 int processState = PROCESS_STATE_UNSPECIFIED; 404 double powerMah = 0; 405 long durationMs = 0; 406 int model = BatteryConsumer.POWER_MODEL_UNDEFINED; 407 for (int i = 0; i < parser.getAttributeCount(); i++) { 408 switch (parser.getAttributeName(i)) { 409 case BatteryUsageStats.XML_ATTR_ID: 410 componentId = parser.getAttributeInt(i); 411 break; 412 case BatteryUsageStats.XML_ATTR_PROCESS_STATE: 413 processState = parser.getAttributeInt(i); 414 break; 415 case BatteryUsageStats.XML_ATTR_POWER: 416 powerMah = parser.getAttributeDouble(i); 417 break; 418 case BatteryUsageStats.XML_ATTR_DURATION: 419 durationMs = parser.getAttributeLong(i); 420 break; 421 case BatteryUsageStats.XML_ATTR_MODEL: 422 model = parser.getAttributeInt(i); 423 break; 424 } 425 } 426 final BatteryConsumer.Key key = 427 builder.mData.getKey(componentId, processState); 428 builder.setConsumedPower(key, powerMah, model); 429 builder.setUsageDurationMillis(key, durationMs); 430 break; 431 } 432 case BatteryUsageStats.XML_TAG_CUSTOM_COMPONENT: { 433 int componentId = -1; 434 double powerMah = 0; 435 long durationMs = 0; 436 for (int i = 0; i < parser.getAttributeCount(); i++) { 437 switch (parser.getAttributeName(i)) { 438 case BatteryUsageStats.XML_ATTR_ID: 439 componentId = parser.getAttributeInt(i); 440 break; 441 case BatteryUsageStats.XML_ATTR_POWER: 442 powerMah = parser.getAttributeDouble(i); 443 break; 444 case BatteryUsageStats.XML_ATTR_DURATION: 445 durationMs = parser.getAttributeLong(i); 446 break; 447 } 448 } 449 builder.setConsumedPowerForCustomComponent(componentId, powerMah); 450 builder.setUsageDurationForCustomComponentMillis(componentId, durationMs); 451 break; 452 } 453 } 454 } 455 eventType = parser.next(); 456 } 457 } 458 459 /** 460 * Builder for PowerComponents. 461 */ 462 static final class Builder { 463 private static final byte POWER_MODEL_UNINITIALIZED = -1; 464 465 private final BatteryConsumer.BatteryConsumerData mData; 466 private final double mMinConsumedPowerThreshold; 467 Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold)468 Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) { 469 mData = data; 470 mMinConsumedPowerThreshold = minConsumedPowerThreshold; 471 for (BatteryConsumer.Key[] keys : mData.layout.keys) { 472 for (BatteryConsumer.Key key : keys) { 473 if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { 474 mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED); 475 } 476 } 477 } 478 } 479 480 @NonNull setConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel)481 public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower, 482 int powerModel) { 483 mData.putDouble(key.mPowerColumnIndex, componentPower); 484 if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { 485 mData.putInt(key.mPowerModelColumnIndex, powerModel); 486 } 487 return this; 488 } 489 490 @NonNull addConsumedPower(BatteryConsumer.Key key, double componentPower, int powerModel)491 public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower, 492 int powerModel) { 493 mData.putDouble(key.mPowerColumnIndex, 494 mData.getDouble(key.mPowerColumnIndex) + componentPower); 495 if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { 496 mData.putInt(key.mPowerModelColumnIndex, powerModel); 497 } 498 return this; 499 } 500 501 /** 502 * Sets the amount of drain attributed to the specified custom drain type. 503 * 504 * @param componentId The ID of the custom power component. 505 * @param componentPower Amount of consumed power in mAh. 506 */ 507 @NonNull setConsumedPowerForCustomComponent(int componentId, double componentPower)508 public Builder setConsumedPowerForCustomComponent(int componentId, double componentPower) { 509 final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; 510 if (index < 0 || index >= mData.layout.customPowerComponentCount) { 511 throw new IllegalArgumentException( 512 "Unsupported custom power component ID: " + componentId); 513 } 514 mData.putDouble(mData.layout.firstCustomConsumedPowerColumn + index, componentPower); 515 return this; 516 } 517 518 @NonNull setUsageDurationMillis(BatteryConsumer.Key key, long componentUsageDurationMillis)519 public Builder setUsageDurationMillis(BatteryConsumer.Key key, 520 long componentUsageDurationMillis) { 521 mData.putLong(key.mDurationColumnIndex, componentUsageDurationMillis); 522 return this; 523 } 524 525 /** 526 * Sets the amount of time used by the specified custom component. 527 * 528 * @param componentId The ID of the custom power component. 529 * @param componentUsageDurationMillis Amount of time in milliseconds. 530 */ 531 @NonNull setUsageDurationForCustomComponentMillis(int componentId, long componentUsageDurationMillis)532 public Builder setUsageDurationForCustomComponentMillis(int componentId, 533 long componentUsageDurationMillis) { 534 final int index = componentId - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; 535 if (index < 0 || index >= mData.layout.customPowerComponentCount) { 536 throw new IllegalArgumentException( 537 "Unsupported custom power component ID: " + componentId); 538 } 539 540 mData.putLong(mData.layout.firstCustomUsageDurationColumn + index, 541 componentUsageDurationMillis); 542 return this; 543 } 544 addPowerAndDuration(PowerComponents.Builder other)545 public void addPowerAndDuration(PowerComponents.Builder other) { 546 addPowerAndDuration(other.mData); 547 } 548 addPowerAndDuration(PowerComponents other)549 public void addPowerAndDuration(PowerComponents other) { 550 addPowerAndDuration(other.mData); 551 } 552 addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData)553 private void addPowerAndDuration(BatteryConsumer.BatteryConsumerData otherData) { 554 if (mData.layout.customPowerComponentCount 555 != otherData.layout.customPowerComponentCount) { 556 throw new IllegalArgumentException( 557 "Number of custom power components does not match: " 558 + otherData.layout.customPowerComponentCount 559 + ", expected: " + mData.layout.customPowerComponentCount); 560 } 561 562 for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0; 563 componentId--) { 564 final BatteryConsumer.Key[] keys = mData.layout.keys[componentId]; 565 for (BatteryConsumer.Key key: keys) { 566 BatteryConsumer.Key otherKey = null; 567 for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) { 568 if (aKey.equals(key)) { 569 otherKey = aKey; 570 break; 571 } 572 } 573 574 if (otherKey == null) { 575 continue; 576 } 577 578 mData.putDouble(key.mPowerColumnIndex, 579 mData.getDouble(key.mPowerColumnIndex) 580 + otherData.getDouble(otherKey.mPowerColumnIndex)); 581 mData.putLong(key.mDurationColumnIndex, 582 mData.getLong(key.mDurationColumnIndex) 583 + otherData.getLong(otherKey.mDurationColumnIndex)); 584 585 if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { 586 continue; 587 } 588 589 boolean undefined = false; 590 if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { 591 undefined = true; 592 } else { 593 final int powerModel = mData.getInt(key.mPowerModelColumnIndex); 594 int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex); 595 if (powerModel == POWER_MODEL_UNINITIALIZED) { 596 mData.putInt(key.mPowerModelColumnIndex, otherPowerModel); 597 } else if (powerModel != otherPowerModel 598 && otherPowerModel != POWER_MODEL_UNINITIALIZED) { 599 undefined = true; 600 } 601 } 602 603 if (undefined) { 604 mData.putInt(key.mPowerModelColumnIndex, 605 BatteryConsumer.POWER_MODEL_UNDEFINED); 606 } 607 } 608 } 609 610 for (int i = mData.layout.customPowerComponentCount - 1; i >= 0; i--) { 611 final int powerColumnIndex = mData.layout.firstCustomConsumedPowerColumn + i; 612 final int otherPowerColumnIndex = 613 otherData.layout.firstCustomConsumedPowerColumn + i; 614 mData.putDouble(powerColumnIndex, 615 mData.getDouble(powerColumnIndex) + otherData.getDouble( 616 otherPowerColumnIndex)); 617 618 final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i; 619 final int otherDurationColumnIndex = 620 otherData.layout.firstCustomUsageDurationColumn + i; 621 mData.putLong(usageColumnIndex, 622 mData.getLong(usageColumnIndex) + otherData.getLong( 623 otherDurationColumnIndex) 624 ); 625 } 626 } 627 628 /** 629 * Returns the total power accumulated by this builder so far. It may change 630 * by the time the {@code build()} method is called. 631 */ getTotalPower()632 public double getTotalPower() { 633 double totalPowerMah = 0; 634 for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; 635 componentId++) { 636 totalPowerMah += mData.getDouble( 637 mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex); 638 } 639 for (int i = 0; i < mData.layout.customPowerComponentCount; i++) { 640 totalPowerMah += mData.getDouble( 641 mData.layout.firstCustomConsumedPowerColumn + i); 642 } 643 return totalPowerMah; 644 } 645 646 /** 647 * Creates a read-only object out of the Builder values. 648 */ 649 @NonNull build()650 public PowerComponents build() { 651 for (BatteryConsumer.Key[] keys : mData.layout.keys) { 652 for (BatteryConsumer.Key key : keys) { 653 if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { 654 if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) { 655 mData.putInt(key.mPowerModelColumnIndex, 656 BatteryConsumer.POWER_MODEL_UNDEFINED); 657 } 658 } 659 660 if (mMinConsumedPowerThreshold != 0) { 661 if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) { 662 mData.putDouble(key.mPowerColumnIndex, 0); 663 } 664 } 665 } 666 } 667 668 if (mData.getDouble(mData.layout.totalConsumedPowerColumnIndex) == 0) { 669 mData.putDouble(mData.layout.totalConsumedPowerColumnIndex, getTotalPower()); 670 } 671 return new PowerComponents(this); 672 } 673 } 674 } 675