| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.os; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.database.Cursor; |
| import android.database.CursorWindow; |
| import android.util.Range; |
| import android.util.SparseArray; |
| import android.util.proto.ProtoOutputStream; |
| |
| import com.android.internal.os.BatteryStatsHistory; |
| import com.android.internal.os.BatteryStatsHistoryIterator; |
| import com.android.internal.os.MonotonicClock; |
| import com.android.modules.utils.TypedXmlPullParser; |
| import com.android.modules.utils.TypedXmlSerializer; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.Closeable; |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| /** |
| * Contains a snapshot of battery attribution data, on a per-subsystem and per-UID basis. |
| * <p> |
| * The totals for the entire device are returned as AggregateBatteryConsumers, which can be |
| * obtained by calling {@link #getAggregateBatteryConsumer(int)}. |
| * <p> |
| * Power attributed to individual apps is returned as UidBatteryConsumers, see |
| * {@link #getUidBatteryConsumers()}. |
| * |
| * @hide |
| */ |
| @android.ravenwood.annotation.RavenwoodKeepWholeClass |
| public final class BatteryUsageStats implements Parcelable, Closeable { |
| |
| /** |
| * Scope of battery stats included in a BatteryConsumer: the entire device, just |
| * the apps, etc. |
| * |
| * @hide |
| */ |
| @IntDef(prefix = {"AGGREGATE_BATTERY_CONSUMER_SCOPE_"}, value = { |
| AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE, |
| AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public static @interface AggregateBatteryConsumerScope { |
| } |
| |
| /** |
| * Power consumption by the entire device, since last charge. The power usage in this |
| * scope includes both the power attributed to apps and the power unattributed to any |
| * apps. |
| */ |
| public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE = 0; |
| |
| /** |
| * Aggregated power consumed by all applications, combined, since last charge. This is |
| * the sum of power reported in UidBatteryConsumers. |
| */ |
| public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS = 1; |
| |
| public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT = 2; |
| |
| // XML tags and attributes for BatteryUsageStats persistence |
| static final String XML_TAG_BATTERY_USAGE_STATS = "battery_usage_stats"; |
| static final String XML_TAG_AGGREGATE = "aggregate"; |
| static final String XML_TAG_UID = "uid"; |
| static final String XML_TAG_USER = "user"; |
| static final String XML_TAG_POWER_COMPONENTS = "power_components"; |
| static final String XML_TAG_COMPONENT = "component"; |
| static final String XML_TAG_CUSTOM_COMPONENT = "custom_component"; |
| static final String XML_ATTR_ID = "id"; |
| static final String XML_ATTR_UID = "uid"; |
| static final String XML_ATTR_USER_ID = "user_id"; |
| static final String XML_ATTR_SCOPE = "scope"; |
| static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_"; |
| static final String XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA = "includes_proc_state_data"; |
| static final String XML_ATTR_START_TIMESTAMP = "start_timestamp"; |
| static final String XML_ATTR_END_TIMESTAMP = "end_timestamp"; |
| static final String XML_ATTR_PROCESS_STATE = "process_state"; |
| static final String XML_ATTR_POWER = "power"; |
| static final String XML_ATTR_DURATION = "duration"; |
| static final String XML_ATTR_MODEL = "model"; |
| static final String XML_ATTR_BATTERY_CAPACITY = "battery_capacity"; |
| static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct"; |
| static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower"; |
| static final String XML_ATTR_DISCHARGE_UPPER = "discharge_upper"; |
| static final String XML_ATTR_DISCHARGE_DURATION = "discharge_duration"; |
| static final String XML_ATTR_BATTERY_REMAINING = "battery_remaining"; |
| static final String XML_ATTR_CHARGE_REMAINING = "charge_remaining"; |
| static final String XML_ATTR_HIGHEST_DRAIN_PACKAGE = "highest_drain_package"; |
| static final String XML_ATTR_TIME_IN_FOREGROUND = "time_in_foreground"; |
| static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background"; |
| static final String XML_ATTR_TIME_IN_FOREGROUND_SERVICE = "time_in_foreground_service"; |
| |
| // We need about 700 bytes per UID |
| private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 5_000 * 700; |
| |
| private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000; |
| |
| private static final int[] UID_USAGE_TIME_PROCESS_STATES = { |
| BatteryConsumer.PROCESS_STATE_FOREGROUND, |
| BatteryConsumer.PROCESS_STATE_BACKGROUND, |
| BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE |
| }; |
| |
| private final int mDischargePercentage; |
| private final double mBatteryCapacityMah; |
| private final long mStatsStartTimestampMs; |
| private final long mStatsEndTimestampMs; |
| private final long mStatsDurationMs; |
| private final double mDischargedPowerLowerBound; |
| private final double mDischargedPowerUpperBound; |
| private final long mDischargeDurationMs; |
| private final long mBatteryTimeRemainingMs; |
| private final long mChargeTimeRemainingMs; |
| private final String[] mCustomPowerComponentNames; |
| private final boolean mIncludesPowerModels; |
| private final boolean mIncludesProcessStateData; |
| private final List<UidBatteryConsumer> mUidBatteryConsumers; |
| private final List<UserBatteryConsumer> mUserBatteryConsumers; |
| private final AggregateBatteryConsumer[] mAggregateBatteryConsumers; |
| private final BatteryStatsHistory mBatteryStatsHistory; |
| private CursorWindow mBatteryConsumersCursorWindow; |
| |
| private BatteryUsageStats(@NonNull Builder builder) { |
| mStatsStartTimestampMs = builder.mStatsStartTimestampMs; |
| mStatsEndTimestampMs = builder.mStatsEndTimestampMs; |
| mStatsDurationMs = builder.getStatsDuration(); |
| mBatteryCapacityMah = builder.mBatteryCapacityMah; |
| mDischargePercentage = builder.mDischargePercentage; |
| mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah; |
| mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah; |
| mDischargeDurationMs = builder.mDischargeDurationMs; |
| mBatteryStatsHistory = builder.mBatteryStatsHistory; |
| mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs; |
| mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs; |
| mCustomPowerComponentNames = builder.mCustomPowerComponentNames; |
| mIncludesPowerModels = builder.mIncludePowerModels; |
| mIncludesProcessStateData = builder.mIncludesProcessStateData; |
| mBatteryConsumersCursorWindow = builder.mBatteryConsumersCursorWindow; |
| |
| double totalPowerMah = 0; |
| final int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size(); |
| mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount); |
| for (int i = 0; i < uidBatteryConsumerCount; i++) { |
| final UidBatteryConsumer.Builder uidBatteryConsumerBuilder = |
| builder.mUidBatteryConsumerBuilders.valueAt(i); |
| if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) { |
| final UidBatteryConsumer consumer = uidBatteryConsumerBuilder.build(); |
| totalPowerMah += consumer.getConsumedPower(); |
| mUidBatteryConsumers.add(consumer); |
| } |
| } |
| |
| final int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size(); |
| mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount); |
| for (int i = 0; i < userBatteryConsumerCount; i++) { |
| final UserBatteryConsumer consumer = |
| builder.mUserBatteryConsumerBuilders.valueAt(i).build(); |
| totalPowerMah += consumer.getConsumedPower(); |
| mUserBatteryConsumers.add(consumer); |
| } |
| |
| builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) |
| .setConsumedPower(totalPowerMah); |
| |
| mAggregateBatteryConsumers = |
| new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT]; |
| for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) { |
| mAggregateBatteryConsumers[i] = builder.mAggregateBatteryConsumersBuilders[i].build(); |
| } |
| } |
| |
| /** |
| * Timestamp (as returned by System.currentTimeMillis()) of the latest battery stats reset, in |
| * milliseconds. |
| */ |
| public long getStatsStartTimestamp() { |
| return mStatsStartTimestampMs; |
| } |
| |
| /** |
| * Timestamp (as returned by System.currentTimeMillis()) of when the stats snapshot was taken, |
| * in milliseconds. |
| */ |
| public long getStatsEndTimestamp() { |
| return mStatsEndTimestampMs; |
| } |
| |
| /** |
| * Returns the duration of the stats session captured by this BatteryUsageStats. |
| * In rare cases, statsDuration != statsEndTimestamp - statsStartTimestamp. This may |
| * happen when BatteryUsageStats represents an accumulation of data across multiple |
| * non-contiguous sessions. |
| */ |
| public long getStatsDuration() { |
| return mStatsDurationMs; |
| } |
| |
| /** |
| * Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully |
| * charged), in mAh |
| */ |
| public double getConsumedPower() { |
| return mAggregateBatteryConsumers[AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE] |
| .getConsumedPower(); |
| } |
| |
| /** |
| * Returns battery capacity in milli-amp-hours. |
| */ |
| public double getBatteryCapacity() { |
| return mBatteryCapacityMah; |
| } |
| |
| /** |
| * Portion of battery charge drained since BatteryStats reset (e.g. due to being fully |
| * charged), as percentage of the full charge in the range [0:100]. May exceed 100 if |
| * the device repeatedly charged and discharged prior to the reset. |
| */ |
| public int getDischargePercentage() { |
| return mDischargePercentage; |
| } |
| |
| /** |
| * Returns the discharged power since BatteryStats were last reset, in mAh as an estimated |
| * range. |
| */ |
| public Range<Double> getDischargedPowerRange() { |
| return Range.create(mDischargedPowerLowerBound, mDischargedPowerUpperBound); |
| } |
| |
| /** |
| * Returns the total amount of time the battery was discharging. |
| */ |
| public long getDischargeDurationMs() { |
| return mDischargeDurationMs; |
| } |
| |
| /** |
| * Returns an approximation for how much run time (in milliseconds) is remaining on |
| * the battery. Returns -1 if no time can be computed: either there is not |
| * enough current data to make a decision, or the battery is currently |
| * charging. |
| */ |
| public long getBatteryTimeRemainingMs() { |
| return mBatteryTimeRemainingMs; |
| } |
| |
| /** |
| * Returns an approximation for how much time (in milliseconds) remains until the battery |
| * is fully charged. Returns -1 if no time can be computed: either there is not |
| * enough current data to make a decision, or the battery is currently discharging. |
| */ |
| public long getChargeTimeRemainingMs() { |
| return mChargeTimeRemainingMs; |
| } |
| |
| /** |
| * Returns a battery consumer for the specified battery consumer type. |
| */ |
| public AggregateBatteryConsumer getAggregateBatteryConsumer( |
| @AggregateBatteryConsumerScope int scope) { |
| return mAggregateBatteryConsumers[scope]; |
| } |
| |
| @NonNull |
| public List<UidBatteryConsumer> getUidBatteryConsumers() { |
| return mUidBatteryConsumers; |
| } |
| |
| @NonNull |
| public List<UserBatteryConsumer> getUserBatteryConsumers() { |
| return mUserBatteryConsumers; |
| } |
| |
| /** |
| * Returns the names of custom power components in order, so the first name in the array |
| * corresponds to the custom componentId |
| * {@link BatteryConsumer#FIRST_CUSTOM_POWER_COMPONENT_ID}. |
| */ |
| @NonNull |
| public String[] getCustomPowerComponentNames() { |
| return mCustomPowerComponentNames; |
| } |
| |
| public boolean isProcessStateDataIncluded() { |
| return mIncludesProcessStateData; |
| } |
| |
| /** |
| * Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s. |
| */ |
| @NonNull |
| public BatteryStatsHistoryIterator iterateBatteryStatsHistory() { |
| if (mBatteryStatsHistory == null) { |
| throw new IllegalStateException( |
| "Battery history was not requested in the BatteryUsageStatsQuery"); |
| } |
| return new BatteryStatsHistoryIterator(mBatteryStatsHistory, 0, MonotonicClock.UNDEFINED); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| private BatteryUsageStats(@NonNull Parcel source) { |
| mStatsStartTimestampMs = source.readLong(); |
| mStatsEndTimestampMs = source.readLong(); |
| mStatsDurationMs = source.readLong(); |
| mBatteryCapacityMah = source.readDouble(); |
| mDischargePercentage = source.readInt(); |
| mDischargedPowerLowerBound = source.readDouble(); |
| mDischargedPowerUpperBound = source.readDouble(); |
| mDischargeDurationMs = source.readLong(); |
| mBatteryTimeRemainingMs = source.readLong(); |
| mChargeTimeRemainingMs = source.readLong(); |
| mCustomPowerComponentNames = source.readStringArray(); |
| mIncludesPowerModels = source.readBoolean(); |
| mIncludesProcessStateData = source.readBoolean(); |
| |
| mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source); |
| BatteryConsumer.BatteryConsumerDataLayout dataLayout = |
| BatteryConsumer.createBatteryConsumerDataLayout(mCustomPowerComponentNames, |
| mIncludesPowerModels, mIncludesProcessStateData); |
| |
| final int numRows = mBatteryConsumersCursorWindow.getNumRows(); |
| |
| mAggregateBatteryConsumers = |
| new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT]; |
| mUidBatteryConsumers = new ArrayList<>(numRows); |
| mUserBatteryConsumers = new ArrayList<>(); |
| |
| for (int i = 0; i < numRows; i++) { |
| final BatteryConsumer.BatteryConsumerData data = |
| new BatteryConsumer.BatteryConsumerData(mBatteryConsumersCursorWindow, i, |
| dataLayout); |
| |
| int consumerType = mBatteryConsumersCursorWindow.getInt(i, |
| BatteryConsumer.COLUMN_INDEX_BATTERY_CONSUMER_TYPE); |
| switch (consumerType) { |
| case AggregateBatteryConsumer.CONSUMER_TYPE_AGGREGATE: { |
| final AggregateBatteryConsumer consumer = new AggregateBatteryConsumer(data); |
| mAggregateBatteryConsumers[consumer.getScope()] = consumer; |
| break; |
| } |
| case UidBatteryConsumer.CONSUMER_TYPE_UID: { |
| mUidBatteryConsumers.add(new UidBatteryConsumer(data)); |
| break; |
| } |
| case UserBatteryConsumer.CONSUMER_TYPE_USER: |
| mUserBatteryConsumers.add(new UserBatteryConsumer(data)); |
| break; |
| } |
| } |
| |
| if (source.readBoolean()) { |
| mBatteryStatsHistory = BatteryStatsHistory.createFromBatteryUsageStatsParcel(source); |
| } else { |
| mBatteryStatsHistory = null; |
| } |
| } |
| |
| @Override |
| public void writeToParcel(@NonNull Parcel dest, int flags) { |
| dest.writeLong(mStatsStartTimestampMs); |
| dest.writeLong(mStatsEndTimestampMs); |
| dest.writeLong(mStatsDurationMs); |
| dest.writeDouble(mBatteryCapacityMah); |
| dest.writeInt(mDischargePercentage); |
| dest.writeDouble(mDischargedPowerLowerBound); |
| dest.writeDouble(mDischargedPowerUpperBound); |
| dest.writeLong(mDischargeDurationMs); |
| dest.writeLong(mBatteryTimeRemainingMs); |
| dest.writeLong(mChargeTimeRemainingMs); |
| dest.writeStringArray(mCustomPowerComponentNames); |
| dest.writeBoolean(mIncludesPowerModels); |
| dest.writeBoolean(mIncludesProcessStateData); |
| |
| mBatteryConsumersCursorWindow.writeToParcel(dest, flags); |
| |
| if (mBatteryStatsHistory != null) { |
| dest.writeBoolean(true); |
| mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest); |
| } else { |
| dest.writeBoolean(false); |
| } |
| } |
| |
| @NonNull |
| public static final Creator<BatteryUsageStats> CREATOR = new Creator<BatteryUsageStats>() { |
| public BatteryUsageStats createFromParcel(@NonNull Parcel source) { |
| return new BatteryUsageStats(source); |
| } |
| |
| public BatteryUsageStats[] newArray(int size) { |
| return new BatteryUsageStats[size]; |
| } |
| }; |
| |
| /** Returns a proto (as used for atoms.proto) corresponding to this BatteryUsageStats. */ |
| public byte[] getStatsProto() { |
| // ProtoOutputStream.getRawSize() returns the buffer size before compaction. |
| // BatteryUsageStats contains a lot of integers, so compaction of integers to |
| // varint reduces the size of the proto buffer by as much as 50%. |
| int maxRawSize = (int) (STATSD_PULL_ATOM_MAX_BYTES * 1.75); |
| // Limit the number of attempts in order to prevent an infinite loop |
| for (int i = 0; i < 3; i++) { |
| final ProtoOutputStream proto = new ProtoOutputStream(); |
| writeStatsProto(proto, maxRawSize); |
| |
| final int rawSize = proto.getRawSize(); |
| final byte[] protoOutput = proto.getBytes(); |
| |
| if (protoOutput.length <= STATSD_PULL_ATOM_MAX_BYTES) { |
| return protoOutput; |
| } |
| |
| // Adjust maxRawSize proportionately and try again. |
| maxRawSize = |
| (int) ((long) STATSD_PULL_ATOM_MAX_BYTES * rawSize / protoOutput.length - 1024); |
| } |
| |
| // Fallback: if we have failed to generate a proto smaller than STATSD_PULL_ATOM_MAX_BYTES, |
| // just generate a proto with the _rawSize_ of STATSD_PULL_ATOM_MAX_BYTES, which is |
| // guaranteed to produce a compacted proto (significantly) smaller than |
| // STATSD_PULL_ATOM_MAX_BYTES. |
| final ProtoOutputStream proto = new ProtoOutputStream(); |
| writeStatsProto(proto, STATSD_PULL_ATOM_MAX_BYTES); |
| return proto.getBytes(); |
| } |
| |
| /** |
| * Writes contents in a binary protobuffer format, using |
| * the android.os.BatteryUsageStatsAtomsProto proto. |
| */ |
| public void dumpToProto(FileDescriptor fd) { |
| final ProtoOutputStream proto = new ProtoOutputStream(fd); |
| writeStatsProto(proto, /* max size */ Integer.MAX_VALUE); |
| proto.flush(); |
| } |
| |
| @NonNull |
| private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) { |
| final AggregateBatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer( |
| AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); |
| |
| proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp()); |
| proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, getStatsEndTimestamp()); |
| proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration()); |
| proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE, |
| getDischargePercentage()); |
| proto.write(BatteryUsageStatsAtomsProto.DISCHARGE_DURATION_MILLIS, |
| getDischargeDurationMs()); |
| deviceBatteryConsumer.writeStatsProto(proto, |
| BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER); |
| if (mIncludesPowerModels) { |
| deviceBatteryConsumer.writePowerComponentModelProto(proto); |
| } |
| writeUidBatteryConsumersProto(proto, maxRawSize); |
| } |
| |
| /** |
| * Writes the UidBatteryConsumers data, held by this BatteryUsageStats, to the proto (as used |
| * for atoms.proto). |
| */ |
| private void writeUidBatteryConsumersProto(ProtoOutputStream proto, int maxRawSize) { |
| final List<UidBatteryConsumer> consumers = getUidBatteryConsumers(); |
| // Order consumers by descending weight (a combination of consumed power and usage time) |
| consumers.sort(Comparator.comparingDouble(this::getUidBatteryConsumerWeight).reversed()); |
| |
| final int size = consumers.size(); |
| for (int i = 0; i < size; i++) { |
| final UidBatteryConsumer consumer = consumers.get(i); |
| |
| final long fgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND); |
| final long bgMs = consumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND); |
| final boolean hasBaseData = consumer.hasStatsProtoData(); |
| |
| if (fgMs == 0 && bgMs == 0 && !hasBaseData) { |
| continue; |
| } |
| |
| final long token = proto.start(BatteryUsageStatsAtomsProto.UID_BATTERY_CONSUMERS); |
| proto.write( |
| BatteryUsageStatsAtomsProto.UidBatteryConsumer.UID, |
| consumer.getUid()); |
| if (hasBaseData) { |
| consumer.writeStatsProto(proto, |
| BatteryUsageStatsAtomsProto.UidBatteryConsumer.BATTERY_CONSUMER_DATA); |
| } |
| proto.write( |
| BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_FOREGROUND_MILLIS, |
| fgMs); |
| proto.write( |
| BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS, |
| bgMs); |
| for (int processState : UID_USAGE_TIME_PROCESS_STATES) { |
| final long timeInStateMillis = consumer.getTimeInProcessStateMs(processState); |
| if (timeInStateMillis <= 0) { |
| continue; |
| } |
| final long timeInStateToken = proto.start( |
| BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_STATE); |
| proto.write( |
| BatteryUsageStatsAtomsProto.UidBatteryConsumer.TimeInState.PROCESS_STATE, |
| processState); |
| proto.write( |
| BatteryUsageStatsAtomsProto.UidBatteryConsumer.TimeInState |
| .TIME_IN_STATE_MILLIS, |
| timeInStateMillis); |
| proto.end(timeInStateToken); |
| } |
| proto.end(token); |
| |
| if (proto.getRawSize() >= maxRawSize) { |
| break; |
| } |
| } |
| } |
| |
| private static final double WEIGHT_CONSUMED_POWER = 1; |
| // Weight one hour in foreground the same as 100 mAh of power drain |
| private static final double WEIGHT_FOREGROUND_STATE = 100.0 / (1 * 60 * 60 * 1000); |
| // Weight one hour in background the same as 300 mAh of power drain |
| private static final double WEIGHT_BACKGROUND_STATE = 300.0 / (1 * 60 * 60 * 1000); |
| |
| /** |
| * Computes the weight associated with a UidBatteryConsumer, which is used for sorting. |
| * We want applications with the largest consumed power as well as applications |
| * with the highest usage time to be included in the statsd atom. |
| */ |
| private double getUidBatteryConsumerWeight(UidBatteryConsumer uidBatteryConsumer) { |
| final double consumedPower = uidBatteryConsumer.getConsumedPower(); |
| final long timeInForeground = |
| uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND); |
| final long timeInBackground = |
| uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND); |
| return consumedPower * WEIGHT_CONSUMED_POWER |
| + timeInForeground * WEIGHT_FOREGROUND_STATE |
| + timeInBackground * WEIGHT_BACKGROUND_STATE; |
| } |
| |
| /** |
| * Prints the stats in a human-readable format. |
| */ |
| public void dump(PrintWriter pw, String prefix) { |
| pw.print(prefix); |
| pw.println(" Estimated power use (mAh):"); |
| pw.print(prefix); |
| pw.print(" Capacity: "); |
| pw.print(BatteryStats.formatCharge(getBatteryCapacity())); |
| pw.print(", Computed drain: "); |
| pw.print(BatteryStats.formatCharge(getConsumedPower())); |
| final Range<Double> dischargedPowerRange = getDischargedPowerRange(); |
| pw.print(", actual drain: "); |
| pw.print(BatteryStats.formatCharge(dischargedPowerRange.getLower())); |
| if (!dischargedPowerRange.getLower().equals(dischargedPowerRange.getUpper())) { |
| pw.print("-"); |
| pw.print(BatteryStats.formatCharge(dischargedPowerRange.getUpper())); |
| } |
| pw.println(); |
| |
| pw.println(" Global"); |
| final BatteryConsumer deviceConsumer = getAggregateBatteryConsumer( |
| AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); |
| final BatteryConsumer appsConsumer = getAggregateBatteryConsumer( |
| AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS); |
| |
| for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; |
| componentId++) { |
| for (BatteryConsumer.Key key : deviceConsumer.getKeys(componentId)) { |
| final double devicePowerMah = deviceConsumer.getConsumedPower(key); |
| final double appsPowerMah = appsConsumer.getConsumedPower(key); |
| if (devicePowerMah == 0 && appsPowerMah == 0) { |
| continue; |
| } |
| |
| String label = BatteryConsumer.powerComponentIdToString(componentId); |
| if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { |
| label = label |
| + "(" + BatteryConsumer.processStateToString(key.processState) + ")"; |
| } |
| printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah, |
| mIncludesPowerModels ? deviceConsumer.getPowerModel(key) |
| : BatteryConsumer.POWER_MODEL_UNDEFINED, |
| deviceConsumer.getUsageDurationMillis(key)); |
| } |
| } |
| |
| for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; |
| componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID |
| + mCustomPowerComponentNames.length; |
| componentId++) { |
| final double devicePowerMah = |
| deviceConsumer.getConsumedPowerForCustomComponent(componentId); |
| final double appsPowerMah = |
| appsConsumer.getConsumedPowerForCustomComponent(componentId); |
| if (devicePowerMah == 0 && appsPowerMah == 0) { |
| continue; |
| } |
| |
| printPowerComponent(pw, prefix, deviceConsumer.getCustomPowerComponentName(componentId), |
| devicePowerMah, appsPowerMah, |
| BatteryConsumer.POWER_MODEL_UNDEFINED, |
| deviceConsumer.getUsageDurationForCustomComponentMillis(componentId)); |
| } |
| |
| dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers()); |
| dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers()); |
| pw.println(); |
| } |
| |
| private void printPowerComponent(PrintWriter pw, String prefix, String label, |
| double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(prefix).append(" ").append(label).append(": ") |
| .append(BatteryStats.formatCharge(devicePowerMah)); |
| if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED |
| && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) { |
| sb.append(" ["); |
| sb.append(BatteryConsumer.powerModelToString(powerModel)); |
| sb.append("]"); |
| } |
| sb.append(" apps: ").append(BatteryStats.formatCharge(appsPowerMah)); |
| if (durationMs != 0) { |
| sb.append(" duration: "); |
| BatteryStats.formatTimeMs(sb, durationMs); |
| } |
| |
| pw.println(sb.toString()); |
| } |
| |
| private void dumpSortedBatteryConsumers(PrintWriter pw, String prefix, |
| List<? extends BatteryConsumer> batteryConsumers) { |
| batteryConsumers.sort( |
| Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower) |
| .reversed()); |
| for (BatteryConsumer consumer : batteryConsumers) { |
| if (consumer.getConsumedPower() == 0) { |
| continue; |
| } |
| pw.print(prefix); |
| pw.print(" "); |
| consumer.dump(pw); |
| pw.println(); |
| } |
| } |
| |
| /** Serializes this object to XML */ |
| public void writeXml(TypedXmlSerializer serializer) throws IOException { |
| serializer.startTag(null, XML_TAG_BATTERY_USAGE_STATS); |
| |
| for (int i = 0; i < mCustomPowerComponentNames.length; i++) { |
| serializer.attribute(null, XML_ATTR_PREFIX_CUSTOM_COMPONENT + i, |
| mCustomPowerComponentNames[i]); |
| } |
| serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, |
| mIncludesProcessStateData); |
| serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs); |
| serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs); |
| serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs); |
| serializer.attributeDouble(null, XML_ATTR_BATTERY_CAPACITY, mBatteryCapacityMah); |
| serializer.attributeInt(null, XML_ATTR_DISCHARGE_PERCENT, mDischargePercentage); |
| serializer.attributeDouble(null, XML_ATTR_DISCHARGE_LOWER, mDischargedPowerLowerBound); |
| serializer.attributeDouble(null, XML_ATTR_DISCHARGE_UPPER, mDischargedPowerUpperBound); |
| serializer.attributeLong(null, XML_ATTR_DISCHARGE_DURATION, mDischargeDurationMs); |
| serializer.attributeLong(null, XML_ATTR_BATTERY_REMAINING, mBatteryTimeRemainingMs); |
| serializer.attributeLong(null, XML_ATTR_CHARGE_REMAINING, mChargeTimeRemainingMs); |
| |
| for (int scope = 0; scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; |
| scope++) { |
| mAggregateBatteryConsumers[scope].writeToXml(serializer, scope); |
| } |
| for (UidBatteryConsumer consumer : mUidBatteryConsumers) { |
| consumer.writeToXml(serializer); |
| } |
| for (UserBatteryConsumer consumer : mUserBatteryConsumers) { |
| consumer.writeToXml(serializer); |
| } |
| serializer.endTag(null, XML_TAG_BATTERY_USAGE_STATS); |
| } |
| |
| /** Parses an XML representation of BatteryUsageStats */ |
| public static BatteryUsageStats createFromXml(TypedXmlPullParser parser) |
| throws XmlPullParserException, IOException { |
| Builder builder = null; |
| int eventType = parser.getEventType(); |
| while (eventType != XmlPullParser.END_DOCUMENT) { |
| if (eventType == XmlPullParser.START_TAG |
| && parser.getName().equals(XML_TAG_BATTERY_USAGE_STATS)) { |
| List<String> customComponentNames = new ArrayList<>(); |
| int i = 0; |
| while (true) { |
| int index = parser.getAttributeIndex(null, |
| XML_ATTR_PREFIX_CUSTOM_COMPONENT + i); |
| if (index == -1) { |
| break; |
| } |
| customComponentNames.add(parser.getAttributeValue(index)); |
| i++; |
| } |
| |
| final boolean includesProcStateData = parser.getAttributeBoolean(null, |
| XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false); |
| |
| builder = new Builder(customComponentNames.toArray(new String[0]), true, |
| includesProcStateData, 0); |
| |
| builder.setStatsStartTimestamp( |
| parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP)); |
| builder.setStatsEndTimestamp( |
| parser.getAttributeLong(null, XML_ATTR_END_TIMESTAMP)); |
| builder.setStatsDuration( |
| parser.getAttributeLong(null, XML_ATTR_DURATION)); |
| builder.setBatteryCapacity( |
| parser.getAttributeDouble(null, XML_ATTR_BATTERY_CAPACITY)); |
| builder.setDischargePercentage( |
| parser.getAttributeInt(null, XML_ATTR_DISCHARGE_PERCENT)); |
| builder.setDischargedPowerRange( |
| parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_LOWER), |
| parser.getAttributeDouble(null, XML_ATTR_DISCHARGE_UPPER)); |
| builder.setDischargeDurationMs( |
| parser.getAttributeLong(null, XML_ATTR_DISCHARGE_DURATION)); |
| builder.setBatteryTimeRemainingMs( |
| parser.getAttributeLong(null, XML_ATTR_BATTERY_REMAINING)); |
| builder.setChargeTimeRemainingMs( |
| parser.getAttributeLong(null, XML_ATTR_CHARGE_REMAINING)); |
| |
| eventType = parser.next(); |
| break; |
| } |
| eventType = parser.next(); |
| } |
| |
| if (builder == null) { |
| throw new XmlPullParserException("No root element"); |
| } |
| |
| while (eventType != XmlPullParser.END_DOCUMENT) { |
| if (eventType == XmlPullParser.START_TAG) { |
| switch (parser.getName()) { |
| case XML_TAG_AGGREGATE: |
| AggregateBatteryConsumer.parseXml(parser, builder); |
| break; |
| case XML_TAG_UID: |
| UidBatteryConsumer.createFromXml(parser, builder); |
| break; |
| case XML_TAG_USER: |
| UserBatteryConsumer.createFromXml(parser, builder); |
| break; |
| } |
| } |
| eventType = parser.next(); |
| } |
| |
| return builder.build(); |
| } |
| |
| @Override |
| public void close() throws IOException { |
| mBatteryConsumersCursorWindow.close(); |
| mBatteryConsumersCursorWindow = null; |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| if (mBatteryConsumersCursorWindow != null) { |
| mBatteryConsumersCursorWindow.close(); |
| } |
| super.finalize(); |
| } |
| |
| @Override |
| public String toString() { |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter(sw); |
| dump(pw, ""); |
| pw.flush(); |
| return sw.toString(); |
| } |
| |
| /** |
| * Builder for BatteryUsageStats. |
| */ |
| public static final class Builder { |
| private final CursorWindow mBatteryConsumersCursorWindow; |
| @NonNull |
| private final String[] mCustomPowerComponentNames; |
| private final boolean mIncludePowerModels; |
| private final boolean mIncludesProcessStateData; |
| private final double mMinConsumedPowerThreshold; |
| private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout; |
| private long mStatsStartTimestampMs; |
| private long mStatsEndTimestampMs; |
| private long mStatsDurationMs = -1; |
| private double mBatteryCapacityMah; |
| private int mDischargePercentage; |
| private double mDischargedPowerLowerBoundMah; |
| private double mDischargedPowerUpperBoundMah; |
| private long mDischargeDurationMs; |
| private long mBatteryTimeRemainingMs = -1; |
| private long mChargeTimeRemainingMs = -1; |
| private final AggregateBatteryConsumer.Builder[] mAggregateBatteryConsumersBuilders = |
| new AggregateBatteryConsumer.Builder[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT]; |
| private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders = |
| new SparseArray<>(); |
| private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders = |
| new SparseArray<>(); |
| private BatteryStatsHistory mBatteryStatsHistory; |
| |
| public Builder(@NonNull String[] customPowerComponentNames) { |
| this(customPowerComponentNames, false, false, 0); |
| } |
| |
| public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels, |
| boolean includeProcessStateData, double minConsumedPowerThreshold) { |
| mBatteryConsumersCursorWindow = |
| new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE); |
| mBatteryConsumerDataLayout = |
| BatteryConsumer.createBatteryConsumerDataLayout(customPowerComponentNames, |
| includePowerModels, includeProcessStateData); |
| mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount); |
| |
| mCustomPowerComponentNames = customPowerComponentNames; |
| mIncludePowerModels = includePowerModels; |
| mIncludesProcessStateData = includeProcessStateData; |
| mMinConsumedPowerThreshold = minConsumedPowerThreshold; |
| for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) { |
| final BatteryConsumer.BatteryConsumerData data = |
| BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, |
| mBatteryConsumerDataLayout); |
| mAggregateBatteryConsumersBuilders[scope] = |
| new AggregateBatteryConsumer.Builder( |
| data, scope, mMinConsumedPowerThreshold); |
| } |
| } |
| |
| public boolean isProcessStateDataNeeded() { |
| return mIncludesProcessStateData; |
| } |
| |
| /** |
| * Constructs a read-only object using the Builder values. |
| */ |
| @NonNull |
| public BatteryUsageStats build() { |
| return new BatteryUsageStats(this); |
| } |
| |
| /** |
| * Sets the battery capacity in milli-amp-hours. |
| */ |
| public Builder setBatteryCapacity(double batteryCapacityMah) { |
| mBatteryCapacityMah = batteryCapacityMah; |
| return this; |
| } |
| |
| /** |
| * Sets the timestamp of the latest battery stats reset, in milliseconds. |
| */ |
| public Builder setStatsStartTimestamp(long statsStartTimestampMs) { |
| mStatsStartTimestampMs = statsStartTimestampMs; |
| return this; |
| } |
| |
| /** |
| * Sets the timestamp of when the battery stats snapshot was taken, in milliseconds. |
| */ |
| public Builder setStatsEndTimestamp(long statsEndTimestampMs) { |
| mStatsEndTimestampMs = statsEndTimestampMs; |
| return this; |
| } |
| |
| /** |
| * Sets the duration of the stats session. The default value of this field is |
| * statsEndTimestamp - statsStartTimestamp. |
| */ |
| public Builder setStatsDuration(long statsDurationMs) { |
| mStatsDurationMs = statsDurationMs; |
| return this; |
| } |
| |
| private long getStatsDuration() { |
| if (mStatsDurationMs != -1) { |
| return mStatsDurationMs; |
| } else { |
| return mStatsEndTimestampMs - mStatsStartTimestampMs; |
| } |
| } |
| |
| /** |
| * Sets the battery discharge amount since BatteryStats reset as percentage of the full |
| * charge. |
| */ |
| @NonNull |
| public Builder setDischargePercentage(int dischargePercentage) { |
| mDischargePercentage = dischargePercentage; |
| return this; |
| } |
| |
| /** |
| * Sets the estimated battery discharge range. |
| */ |
| @NonNull |
| public Builder setDischargedPowerRange(double dischargedPowerLowerBoundMah, |
| double dischargedPowerUpperBoundMah) { |
| mDischargedPowerLowerBoundMah = dischargedPowerLowerBoundMah; |
| mDischargedPowerUpperBoundMah = dischargedPowerUpperBoundMah; |
| return this; |
| } |
| |
| /** |
| * Sets the total battery discharge time, in milliseconds. |
| */ |
| @NonNull |
| public Builder setDischargeDurationMs(long durationMs) { |
| mDischargeDurationMs = durationMs; |
| return this; |
| } |
| |
| /** |
| * Sets an approximation for how much time (in milliseconds) remains until the battery |
| * is fully discharged. |
| */ |
| @NonNull |
| public Builder setBatteryTimeRemainingMs(long batteryTimeRemainingMs) { |
| mBatteryTimeRemainingMs = batteryTimeRemainingMs; |
| return this; |
| } |
| |
| /** |
| * Sets an approximation for how much time (in milliseconds) remains until the battery |
| * is fully charged. |
| */ |
| @NonNull |
| public Builder setChargeTimeRemainingMs(long chargeTimeRemainingMs) { |
| mChargeTimeRemainingMs = chargeTimeRemainingMs; |
| return this; |
| } |
| |
| /** |
| * Sets the parceled recent history. |
| */ |
| @NonNull |
| public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory) { |
| mBatteryStatsHistory = batteryStatsHistory; |
| return this; |
| } |
| |
| /** |
| * Creates or returns an AggregateBatteryConsumer builder, which represents aggregate |
| * battery consumption data for the specified scope. |
| */ |
| @NonNull |
| public AggregateBatteryConsumer.Builder getAggregateBatteryConsumerBuilder( |
| @AggregateBatteryConsumerScope int scope) { |
| return mAggregateBatteryConsumersBuilders[scope]; |
| } |
| |
| /** |
| * Creates or returns a UidBatteryConsumer, which represents battery attribution |
| * data for an individual UID. |
| */ |
| @NonNull |
| public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder( |
| @NonNull BatteryStats.Uid batteryStatsUid) { |
| int uid = batteryStatsUid.getUid(); |
| UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid); |
| if (builder == null) { |
| final BatteryConsumer.BatteryConsumerData data = |
| BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, |
| mBatteryConsumerDataLayout); |
| builder = new UidBatteryConsumer.Builder(data, batteryStatsUid, |
| mMinConsumedPowerThreshold); |
| mUidBatteryConsumerBuilders.put(uid, builder); |
| } |
| return builder; |
| } |
| |
| /** |
| * Creates or returns a UidBatteryConsumer, which represents battery attribution |
| * data for an individual UID. This version of the method is not suitable for use |
| * with PowerCalculators. |
| */ |
| @NonNull |
| public UidBatteryConsumer.Builder getOrCreateUidBatteryConsumerBuilder(int uid) { |
| UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid); |
| if (builder == null) { |
| final BatteryConsumer.BatteryConsumerData data = |
| BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, |
| mBatteryConsumerDataLayout); |
| builder = new UidBatteryConsumer.Builder(data, uid, mMinConsumedPowerThreshold); |
| mUidBatteryConsumerBuilders.put(uid, builder); |
| } |
| return builder; |
| } |
| |
| /** |
| * Creates or returns a UserBatteryConsumer, which represents battery attribution |
| * data for an individual {@link UserHandle}. |
| */ |
| @NonNull |
| public UserBatteryConsumer.Builder getOrCreateUserBatteryConsumerBuilder(int userId) { |
| UserBatteryConsumer.Builder builder = mUserBatteryConsumerBuilders.get(userId); |
| if (builder == null) { |
| final BatteryConsumer.BatteryConsumerData data = |
| BatteryConsumer.BatteryConsumerData.create(mBatteryConsumersCursorWindow, |
| mBatteryConsumerDataLayout); |
| builder = new UserBatteryConsumer.Builder(data, userId, mMinConsumedPowerThreshold); |
| mUserBatteryConsumerBuilders.put(userId, builder); |
| } |
| return builder; |
| } |
| |
| @NonNull |
| public SparseArray<UidBatteryConsumer.Builder> getUidBatteryConsumerBuilders() { |
| return mUidBatteryConsumerBuilders; |
| } |
| |
| /** |
| * Adds battery usage stats from another snapshots. The two snapshots are assumed to be |
| * non-overlapping, meaning that the power consumption estimates and session durations |
| * can be simply summed across the two snapshots. This remains true even if the timestamps |
| * seem to indicate that the sessions are in fact overlapping: timestamps may be off as a |
| * result of realtime clock adjustments by the user or the system. |
| */ |
| @NonNull |
| public Builder add(BatteryUsageStats stats) { |
| if (!Arrays.equals(mCustomPowerComponentNames, stats.mCustomPowerComponentNames)) { |
| throw new IllegalArgumentException( |
| "BatteryUsageStats have different custom power components"); |
| } |
| |
| if (mIncludesProcessStateData && !stats.mIncludesProcessStateData) { |
| throw new IllegalArgumentException( |
| "Added BatteryUsageStats does not include process state data"); |
| } |
| |
| if (mUserBatteryConsumerBuilders.size() != 0 |
| || !stats.getUserBatteryConsumers().isEmpty()) { |
| throw new UnsupportedOperationException( |
| "Combining UserBatteryConsumers is not supported"); |
| } |
| |
| mDischargedPowerLowerBoundMah += stats.mDischargedPowerLowerBound; |
| mDischargedPowerUpperBoundMah += stats.mDischargedPowerUpperBound; |
| mDischargePercentage += stats.mDischargePercentage; |
| mDischargeDurationMs += stats.mDischargeDurationMs; |
| |
| mStatsDurationMs = getStatsDuration() + stats.getStatsDuration(); |
| |
| if (mStatsStartTimestampMs == 0 |
| || stats.mStatsStartTimestampMs < mStatsStartTimestampMs) { |
| mStatsStartTimestampMs = stats.mStatsStartTimestampMs; |
| } |
| |
| final boolean addingLaterSnapshot = stats.mStatsEndTimestampMs > mStatsEndTimestampMs; |
| if (addingLaterSnapshot) { |
| mStatsEndTimestampMs = stats.mStatsEndTimestampMs; |
| } |
| |
| for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) { |
| getAggregateBatteryConsumerBuilder(scope) |
| .add(stats.mAggregateBatteryConsumers[scope]); |
| } |
| |
| for (UidBatteryConsumer consumer : stats.getUidBatteryConsumers()) { |
| getOrCreateUidBatteryConsumerBuilder(consumer.getUid()).add(consumer); |
| } |
| |
| if (addingLaterSnapshot) { |
| mBatteryCapacityMah = stats.mBatteryCapacityMah; |
| mBatteryTimeRemainingMs = stats.mBatteryTimeRemainingMs; |
| mChargeTimeRemainingMs = stats.mChargeTimeRemainingMs; |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Dumps raw contents of the cursor window for debugging. |
| */ |
| void dump(PrintWriter writer) { |
| final int numRows = mBatteryConsumersCursorWindow.getNumRows(); |
| int numColumns = mBatteryConsumerDataLayout.columnCount; |
| for (int i = 0; i < numRows; i++) { |
| StringBuilder sb = new StringBuilder(); |
| for (int j = 0; j < numColumns; j++) { |
| final int type = mBatteryConsumersCursorWindow.getType(i, j); |
| switch (type) { |
| case Cursor.FIELD_TYPE_NULL: |
| sb.append("null, "); |
| break; |
| case Cursor.FIELD_TYPE_INTEGER: |
| sb.append(mBatteryConsumersCursorWindow.getInt(i, j)).append(", "); |
| break; |
| case Cursor.FIELD_TYPE_FLOAT: |
| sb.append(mBatteryConsumersCursorWindow.getFloat(i, j)).append(", "); |
| break; |
| case Cursor.FIELD_TYPE_STRING: |
| sb.append(mBatteryConsumersCursorWindow.getString(i, j)).append(", "); |
| break; |
| case Cursor.FIELD_TYPE_BLOB: |
| sb.append("BLOB, "); |
| break; |
| } |
| } |
| sb.setLength(sb.length() - 2); |
| writer.println(sb); |
| } |
| } |
| } |
| } |