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