/* * 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.annotation.Nullable; import android.text.TextUtils; 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.IOException; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Contains power consumption data attributed to a specific UID. * * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public final class UidBatteryConsumer extends BatteryConsumer { static final int CONSUMER_TYPE_UID = 1; @Retention(RetentionPolicy.SOURCE) @IntDef({ STATE_FOREGROUND, STATE_BACKGROUND }) public @interface State { } /** * The state of an application when it is either running a foreground (top) activity. */ public static final int STATE_FOREGROUND = 0; /** * The state of an application when it is running in the background, including the following * states: * * {@link android.app.ActivityManager#PROCESS_STATE_IMPORTANT_BACKGROUND}, * {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND}, * {@link android.app.ActivityManager#PROCESS_STATE_BACKUP}, * {@link android.app.ActivityManager#PROCESS_STATE_SERVICE}, * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER}, * {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE}. */ public static final int STATE_BACKGROUND = 1; static final int COLUMN_INDEX_UID = BatteryConsumer.COLUMN_COUNT; static final int COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN = COLUMN_INDEX_UID + 1; static final int COLUMN_INDEX_TIME_IN_FOREGROUND = COLUMN_INDEX_UID + 2; static final int COLUMN_INDEX_TIME_IN_BACKGROUND = COLUMN_INDEX_UID + 3; static final int COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE = COLUMN_INDEX_UID + 4; static final int COLUMN_COUNT = BatteryConsumer.COLUMN_COUNT + 5; UidBatteryConsumer(BatteryConsumerData data) { super(data); } private UidBatteryConsumer(@NonNull Builder builder) { super(builder.mData, builder.mPowerComponentsBuilder.build()); } public int getUid() { return mData.getInt(COLUMN_INDEX_UID); } @Nullable public String getPackageWithHighestDrain() { return mData.getString(COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN); } /** * Returns the amount of time in milliseconds this UID spent in the specified state. * @deprecated use {@link #getTimeInProcessStateMs} instead. */ @Deprecated public long getTimeInStateMs(@State int state) { switch (state) { case STATE_BACKGROUND: return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND) + mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE); case STATE_FOREGROUND: return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND); } return 0; } /** * Returns the amount of time in milliseconds this UID spent in the specified process state. */ public long getTimeInProcessStateMs(@ProcessState int state) { switch (state) { case PROCESS_STATE_BACKGROUND: return mData.getInt(COLUMN_INDEX_TIME_IN_BACKGROUND); case PROCESS_STATE_FOREGROUND: return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND); case PROCESS_STATE_FOREGROUND_SERVICE: return mData.getInt(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE); } return 0; } @Override public void dump(PrintWriter pw, boolean skipEmptyComponents) { pw.print("UID "); UserHandle.formatUid(pw, getUid()); pw.print(": "); pw.print(BatteryStats.formatCharge(getConsumedPower())); if (mData.layout.processStateDataIncluded) { StringBuilder sb = new StringBuilder(); appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_FOREGROUND, skipEmptyComponents); appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_BACKGROUND, skipEmptyComponents); appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, skipEmptyComponents); appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_CACHED, skipEmptyComponents); pw.print(sb); } pw.print(" ( "); mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */); pw.print(" ) "); } private void appendProcessStateData(StringBuilder sb, @ProcessState int processState, boolean skipEmptyComponents) { Dimensions dimensions = new Dimensions(POWER_COMPONENT_ANY, processState); final double power = mPowerComponents.getConsumedPower(dimensions); if (power == 0 && skipEmptyComponents) { return; } sb.append(" ").append(processStateToString(processState)).append(": ") .append(BatteryStats.formatCharge(power)); } static UidBatteryConsumer create(BatteryConsumerData data) { return new UidBatteryConsumer(data); } /** Serializes this object to XML */ void writeToXml(TypedXmlSerializer serializer) throws IOException { if (getConsumedPower() == 0) { return; } serializer.startTag(null, BatteryUsageStats.XML_TAG_UID); serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_UID, getUid()); final String packageWithHighestDrain = getPackageWithHighestDrain(); if (!TextUtils.isEmpty(packageWithHighestDrain)) { serializer.attribute(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE, packageWithHighestDrain); } serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND, getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND)); serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND, getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND)); serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE, getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE)); mPowerComponents.writeToXml(serializer); serializer.endTag(null, BatteryUsageStats.XML_TAG_UID); } /** Parses an XML representation and populates the BatteryUsageStats builder */ static void createFromXml(TypedXmlPullParser parser, BatteryUsageStats.Builder builder) throws XmlPullParserException, IOException { final int uid = parser.getAttributeInt(null, BatteryUsageStats.XML_ATTR_UID); final UidBatteryConsumer.Builder consumerBuilder = builder.getOrCreateUidBatteryConsumerBuilder(uid); int eventType = parser.getEventType(); if (eventType != XmlPullParser.START_TAG || !parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) { throw new XmlPullParserException("Invalid XML parser state"); } consumerBuilder.setPackageWithHighestDrain( parser.getAttributeValue(null, BatteryUsageStats.XML_ATTR_HIGHEST_DRAIN_PACKAGE)); consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND, parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND)); consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_BACKGROUND)); consumerBuilder.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE, parser.getAttributeLong(null, BatteryUsageStats.XML_ATTR_TIME_IN_FOREGROUND_SERVICE)); while (!(eventType == XmlPullParser.END_TAG && parser.getName().equals(BatteryUsageStats.XML_TAG_UID)) && eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { if (parser.getName().equals(BatteryUsageStats.XML_TAG_POWER_COMPONENTS)) { PowerComponents.parseXml(parser, consumerBuilder.mPowerComponentsBuilder); } } eventType = parser.next(); } } /** * Builder for UidBatteryConsumer. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class Builder extends BaseBuilder { private static final String PACKAGE_NAME_UNINITIALIZED = ""; private final BatteryStats.Uid mBatteryStatsUid; private final int mUid; private final boolean mIsVirtualUid; private String mPackageWithHighestDrain = PACKAGE_NAME_UNINITIALIZED; private boolean mExcludeFromBatteryUsageStats; public Builder(BatteryConsumerData data, @NonNull BatteryStats.Uid batteryStatsUid, double minConsumedPowerThreshold) { this(data, batteryStatsUid, batteryStatsUid.getUid(), minConsumedPowerThreshold); } public Builder(BatteryConsumerData data, int uid, double minConsumedPowerThreshold) { this(data, null, uid, minConsumedPowerThreshold); } private Builder(BatteryConsumerData data, @Nullable BatteryStats.Uid batteryStatsUid, int uid, double minConsumedPowerThreshold) { super(data, CONSUMER_TYPE_UID, minConsumedPowerThreshold); mBatteryStatsUid = batteryStatsUid; mUid = uid; mIsVirtualUid = mUid == Process.SDK_SANDBOX_VIRTUAL_UID; data.putLong(COLUMN_INDEX_UID, mUid); } @NonNull public BatteryStats.Uid getBatteryStatsUid() { if (mBatteryStatsUid == null) { throw new IllegalStateException( "UidBatteryConsumer.Builder was initialized without a BatteryStats.Uid"); } return mBatteryStatsUid; } public int getUid() { return mUid; } public boolean isVirtualUid() { return mIsVirtualUid; } /** * Sets the name of the package owned by this UID that consumed the highest amount * of power since BatteryStats reset. */ @NonNull public Builder setPackageWithHighestDrain(@Nullable String packageName) { mPackageWithHighestDrain = TextUtils.nullIfEmpty(packageName); return this; } /** * Sets the duration, in milliseconds, that this UID was active in a particular state, * such as foreground or background. * @deprecated use {@link #setTimeInProcessStateMs} instead. */ @Deprecated @NonNull public Builder setTimeInStateMs(@State int state, long timeInStateMs) { switch (state) { case STATE_FOREGROUND: mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND, timeInStateMs); break; case STATE_BACKGROUND: mData.putLong(COLUMN_INDEX_TIME_IN_BACKGROUND, timeInStateMs); break; default: throw new IllegalArgumentException("Unsupported state: " + state); } return this; } /** * Sets the duration, in milliseconds, that this UID was active in a particular process * state, such as foreground service. */ @NonNull public Builder setTimeInProcessStateMs(@ProcessState int state, long timeInProcessStateMs) { switch (state) { case PROCESS_STATE_FOREGROUND: mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND, timeInProcessStateMs); break; case PROCESS_STATE_BACKGROUND: mData.putLong(COLUMN_INDEX_TIME_IN_BACKGROUND, timeInProcessStateMs); break; case PROCESS_STATE_FOREGROUND_SERVICE: mData.putLong(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE, timeInProcessStateMs); break; default: throw new IllegalArgumentException("Unsupported process state: " + state); } return this; } /** * Marks the UidBatteryConsumer for exclusion from the result set. */ public Builder excludeFromBatteryUsageStats() { mExcludeFromBatteryUsageStats = true; return this; } /** * Adds power and usage duration from the supplied UidBatteryConsumer. */ public Builder add(UidBatteryConsumer consumer) { mPowerComponentsBuilder.addPowerAndDuration(consumer.mPowerComponents); setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND, mData.getLong(COLUMN_INDEX_TIME_IN_FOREGROUND) + consumer.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND)); setTimeInProcessStateMs(PROCESS_STATE_BACKGROUND, mData.getLong(COLUMN_INDEX_TIME_IN_BACKGROUND) + consumer.getTimeInProcessStateMs(PROCESS_STATE_BACKGROUND)); setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE, mData.getLong(COLUMN_INDEX_TIME_IN_FOREGROUND_SERVICE) + consumer.getTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE)); if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) { mPackageWithHighestDrain = consumer.getPackageWithHighestDrain(); } else if (!TextUtils.equals(mPackageWithHighestDrain, consumer.getPackageWithHighestDrain())) { // Consider combining two UidBatteryConsumers with this distribution // of power drain between packages: // (package1=100, package2=10) and (package1=100, package2=101). // Since we don't know the actual power distribution between packages at this // point, we have no way to correctly declare package1 as the winner. // The naive logic of picking the consumer with the higher total consumed // power would produce an incorrect result. mPackageWithHighestDrain = null; } return this; } /** * Returns true if this UidBatteryConsumer must be excluded from the * BatteryUsageStats. */ public boolean isExcludedFromBatteryUsageStats() { return mExcludeFromBatteryUsageStats; } /** * Creates a read-only object out of the Builder values. */ @NonNull public UidBatteryConsumer build() { if (mPackageWithHighestDrain == PACKAGE_NAME_UNINITIALIZED) { mPackageWithHighestDrain = null; } if (mPackageWithHighestDrain != null) { mData.putString(COLUMN_INDEX_PACKAGE_WITH_HIGHEST_DRAIN, mPackageWithHighestDrain); } return new UidBatteryConsumer(this); } } }