/* * Copyright (C) 2021 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 com.android.internal.power; import android.annotation.IntDef; import android.os.BatteryStats; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseDoubleArray; import com.android.internal.os.PowerProfile; import com.android.internal.util.XmlUtils; 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; import java.util.Arrays; /** * ModemPowerProfile for handling the modem element in the power_profile.xml */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class ModemPowerProfile { private static final String TAG = "ModemPowerProfile"; private static final String TAG_SLEEP = "sleep"; private static final String TAG_IDLE = "idle"; private static final String TAG_ACTIVE = "active"; private static final String TAG_RECEIVE = "receive"; private static final String TAG_TRANSMIT = "transmit"; private static final String ATTR_RAT = "rat"; private static final String ATTR_NR_FREQUENCY = "nrFrequency"; private static final String ATTR_LEVEL = "level"; /** * A flattened list of the modem power constant extracted from the given XML parser. * * The bitfields of a key describes what its corresponding power constant represents: * [31:28] - {@link ModemDrainType} (max count = 16). * [27:24] - {@link ModemTxLevel} (only for {@link MODEM_DRAIN_TYPE_TX}) (max count = 16). * [23:20] - {@link ModemRatType} (max count = 16). * [19:16] - {@link ModemNrFrequencyRange} (only for {@link MODEM_RAT_TYPE_NR}) * (max count = 16). * [15:0] - RESERVED */ private final SparseDoubleArray mPowerConstants = new SparseDoubleArray(); private static final int MODEM_DRAIN_TYPE_MASK = 0xF000_0000; private static final int MODEM_TX_LEVEL_MASK = 0x0F00_0000; private static final int MODEM_RAT_TYPE_MASK = 0x00F0_0000; private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0x000F_0000; /** * Corresponds to the overall modem battery drain while asleep. */ public static final int MODEM_DRAIN_TYPE_SLEEP = 0x0000_0000; /** * Corresponds to the overall modem battery drain while idle. */ public static final int MODEM_DRAIN_TYPE_IDLE = 0x1000_0000; /** * Corresponds to the modem battery drain while receiving data. A specific Rx battery drain * power constant can be selected using a bitwise OR (|) with {@link ModemRatType} and * {@link ModemNrFrequencyRange} (when applicable). */ public static final int MODEM_DRAIN_TYPE_RX = 0x2000_0000; /** * Corresponds to the modem battery drain while receiving data. * {@link ModemTxLevel} must be specified with this drain type. * Specific Tx battery drain power constanta can be selected using a bitwise OR (|) with * {@link ModemRatType} and {@link ModemNrFrequencyRange} (when applicable). */ public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000; private static final int IGNORE = -1; @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = { MODEM_DRAIN_TYPE_SLEEP, MODEM_DRAIN_TYPE_IDLE, MODEM_DRAIN_TYPE_RX, MODEM_DRAIN_TYPE_TX, }) @Retention(RetentionPolicy.SOURCE) public @interface ModemDrainType { } private static final SparseArray MODEM_DRAIN_TYPE_NAMES = new SparseArray<>(4); static { MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_SLEEP, "SLEEP"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_IDLE, "IDLE"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_RX, "RX"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_TX, "TX"); } /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}. */ public static final int MODEM_TX_LEVEL_0 = 0x0000_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}. */ public static final int MODEM_TX_LEVEL_1 = 0x0100_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}. */ public static final int MODEM_TX_LEVEL_2 = 0x0200_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}. */ public static final int MODEM_TX_LEVEL_3 = 0x0300_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}. */ public static final int MODEM_TX_LEVEL_4 = 0x0400_0000; private static final int MODEM_TX_LEVEL_COUNT = 5; @IntDef(prefix = {"MODEM_TX_LEVEL_"}, value = { MODEM_TX_LEVEL_0, MODEM_TX_LEVEL_1, MODEM_TX_LEVEL_2, MODEM_TX_LEVEL_3, MODEM_TX_LEVEL_4, }) @Retention(RetentionPolicy.SOURCE) public @interface ModemTxLevel { } private static final SparseArray MODEM_TX_LEVEL_NAMES = new SparseArray<>(5); static { MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_0, "0"); MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_1, "1"); MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_2, "2"); MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_3, "3"); MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_4, "4"); } private static final int[] MODEM_TX_LEVEL_MAP = new int[]{ MODEM_TX_LEVEL_0, MODEM_TX_LEVEL_1, MODEM_TX_LEVEL_2, MODEM_TX_LEVEL_3, MODEM_TX_LEVEL_4}; /** * Fallback for any active modem usage that does not match specified Radio Access Technology * (RAT) power constants. */ public static final int MODEM_RAT_TYPE_DEFAULT = 0x0000_0000; /** * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT. */ public static final int MODEM_RAT_TYPE_LTE = 0x0010_0000; /** * Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT. */ public static final int MODEM_RAT_TYPE_NR = 0x0020_0000; @IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = { MODEM_RAT_TYPE_DEFAULT, MODEM_RAT_TYPE_LTE, MODEM_RAT_TYPE_NR, }) @Retention(RetentionPolicy.SOURCE) public @interface ModemRatType { } private static final SparseArray MODEM_RAT_TYPE_NAMES = new SparseArray<>(3); static { MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_DEFAULT, "DEFAULT"); MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_LTE, "LTE"); MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_NR, "NR"); } /** * Fallback for any active 5G modem usage that does not match specified NR frequency power * constants. */ public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0x0000_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}. */ public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 0x0001_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}. */ public static final int MODEM_NR_FREQUENCY_RANGE_MID = 0x0002_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}. */ public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 0x0003_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}. */ public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 0x0004_0000; @IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = { MODEM_RAT_TYPE_DEFAULT, MODEM_NR_FREQUENCY_RANGE_LOW, MODEM_NR_FREQUENCY_RANGE_MID, MODEM_NR_FREQUENCY_RANGE_HIGH, MODEM_NR_FREQUENCY_RANGE_MMWAVE, }) @Retention(RetentionPolicy.SOURCE) public @interface ModemNrFrequencyRange { } private static final SparseArray MODEM_NR_FREQUENCY_RANGE_NAMES = new SparseArray<>(5); static { MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_DEFAULT, "DEFAULT"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_LOW, "LOW"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MID, "MID"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_HIGH, "HIGH"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MMWAVE, "MMWAVE"); } public ModemPowerProfile() { } /** * Generates a ModemPowerProfile object from the element of a power_profile.xml */ public void parseFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { final int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { final String name = parser.getName(); switch (name) { case TAG_SLEEP: if (parser.next() != XmlPullParser.TEXT) { continue; } final String sleepDrain = parser.getText(); setPowerConstant(MODEM_DRAIN_TYPE_SLEEP, sleepDrain); break; case TAG_IDLE: if (parser.next() != XmlPullParser.TEXT) { continue; } final String idleDrain = parser.getText(); setPowerConstant(MODEM_DRAIN_TYPE_IDLE, idleDrain); break; case TAG_ACTIVE: parseActivePowerConstantsFromXml(parser); break; default: Slog.e(TAG, "Unexpected element parsed: " + name); } } } /** Parse the XML element */ private void parseActivePowerConstantsFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { // Parse attributes to get the type of active modem usage the power constants are for. final int ratType; final int nrfType; try { ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_NAMES); if (ratType == MODEM_RAT_TYPE_NR) { nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY, MODEM_NR_FREQUENCY_RANGE_NAMES); } else { nrfType = 0; } } catch (IllegalArgumentException iae) { Slog.e(TAG, "Failed parse to active modem power constants", iae); return; } // Parse and populate the active modem use power constants. final int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { final String name = parser.getName(); switch (name) { case TAG_RECEIVE: if (parser.next() != XmlPullParser.TEXT) { continue; } final String rxDrain = parser.getText(); final int rxKey = MODEM_DRAIN_TYPE_RX | ratType | nrfType; setPowerConstant(rxKey, rxDrain); break; case TAG_TRANSMIT: final int level = XmlUtils.readIntAttribute(parser, ATTR_LEVEL, -1); if (parser.next() != XmlPullParser.TEXT) { continue; } final String txDrain = parser.getText(); if (level < 0 || level >= MODEM_TX_LEVEL_COUNT) { Slog.e(TAG, "Unexpected tx level: " + level + ". Must be between 0 and " + ( MODEM_TX_LEVEL_COUNT - 1)); continue; } final int modemTxLevel = MODEM_TX_LEVEL_MAP[level]; final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType; setPowerConstant(txKey, txDrain); break; default: Slog.e(TAG, "Unexpected element parsed: " + name); } } } private static int getTypeFromAttribute(XmlPullParser parser, String attr, SparseArray names) { final String value = XmlUtils.readStringAttribute(parser, attr); if (value == null) { // Attribute was not specified, just use the default. return 0; } int index = -1; final int size = names.size(); // Manual linear search for string. (SparseArray uses == not equals.) for (int i = 0; i < size; i++) { if (value.equals(names.valueAt(i))) { index = i; } } if (index < 0) { final String[] stringNames = new String[size]; for (int i = 0; i < size; i++) { stringNames[i] = names.valueAt(i); } throw new IllegalArgumentException( "Unexpected " + attr + " value : " + value + ". Acceptable values are " + Arrays.toString(stringNames)); } return names.keyAt(index); } /** * Set the average battery drain in milli-amps of the modem for a given drain type. * * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, * {@link ModemRatType}, and {@link ModemNrFrequencyRange}.key * @param value the battery dram in milli-amps for the given key. */ public void setPowerConstant(int key, String value) { try { mPowerConstants.put(key, Double.valueOf(value)); } catch (Exception e) { Slog.e(TAG, "Failed to set power constant 0x" + Integer.toHexString( key) + "(" + keyToString(key) + ") to " + value, e); } } public static long getAverageBatteryDrainKey(@ModemDrainType int drainType, @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, int txLevel) { long key = PowerProfile.SUBSYSTEM_MODEM; // Attach Modem drain type to the key if specified. if (drainType != IGNORE) { key |= drainType; } // Attach RadioAccessTechnology to the key if specified. switch (rat) { case IGNORE: // do nothing break; case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER: key |= MODEM_RAT_TYPE_DEFAULT; break; case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE: key |= MODEM_RAT_TYPE_LTE; break; case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR: key |= MODEM_RAT_TYPE_NR; break; default: Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat); } // Attach NR Frequency Range to the key if specified. switch (freqRange) { case IGNORE: // do nothing break; case ServiceState.FREQUENCY_RANGE_UNKNOWN: key |= MODEM_NR_FREQUENCY_RANGE_DEFAULT; break; case ServiceState.FREQUENCY_RANGE_LOW: key |= MODEM_NR_FREQUENCY_RANGE_LOW; break; case ServiceState.FREQUENCY_RANGE_MID: key |= MODEM_NR_FREQUENCY_RANGE_MID; break; case ServiceState.FREQUENCY_RANGE_HIGH: key |= MODEM_NR_FREQUENCY_RANGE_HIGH; break; case ServiceState.FREQUENCY_RANGE_MMWAVE: key |= MODEM_NR_FREQUENCY_RANGE_MMWAVE; break; default: Log.w(TAG, "Unexpected NR frequency range : " + freqRange); } // Attach transmission level to the key if specified. switch (txLevel) { case IGNORE: // do nothing break; case 0: key |= MODEM_TX_LEVEL_0; break; case 1: key |= MODEM_TX_LEVEL_1; break; case 2: key |= MODEM_TX_LEVEL_2; break; case 3: key |= MODEM_TX_LEVEL_3; break; case 4: key |= MODEM_TX_LEVEL_4; break; default: Log.w(TAG, "Unexpected transmission level : " + txLevel); } return key; } /** * Returns the average battery drain in milli-amps of the modem for a given drain type. * Returns {@link Double.NaN} if a suitable value is not found for the given key. * * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, * {@link ModemRatType}, and {@link ModemNrFrequencyRange}. */ public double getAverageBatteryDrainMa(int key) { int bestKey = key; double value; value = mPowerConstants.get(bestKey, Double.NaN); if (!Double.isNaN(value)) return value; // The power constant for given key was not explicitly set. Try to fallback to possible // defaults. if ((bestKey & MODEM_NR_FREQUENCY_RANGE_MASK) != MODEM_NR_FREQUENCY_RANGE_DEFAULT) { // Fallback to NR Frequency default value bestKey &= ~MODEM_NR_FREQUENCY_RANGE_MASK; bestKey |= MODEM_NR_FREQUENCY_RANGE_DEFAULT; value = mPowerConstants.get(bestKey, Double.NaN); if (!Double.isNaN(value)) return value; } if ((bestKey & MODEM_RAT_TYPE_MASK) != MODEM_RAT_TYPE_DEFAULT) { // Fallback to RAT default value bestKey &= ~MODEM_RAT_TYPE_MASK; bestKey |= MODEM_RAT_TYPE_DEFAULT; value = mPowerConstants.get(bestKey, Double.NaN); if (!Double.isNaN(value)) return value; } Slog.w(TAG, "getAverageBatteryDrainMaH called with unexpected key: 0x" + Integer.toHexString( key) + ", " + keyToString(key)); return Double.NaN; } /** * Returns a human readable version of a key. */ public static String keyToString(int key) { StringBuilder sb = new StringBuilder(); final int drainType = key & MODEM_DRAIN_TYPE_MASK; appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType); sb.append(","); if (drainType == MODEM_DRAIN_TYPE_TX) { final int txLevel = key & MODEM_TX_LEVEL_MASK; appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel); sb.append(","); } final int ratType = key & MODEM_RAT_TYPE_MASK; appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType); if (ratType == MODEM_RAT_TYPE_NR) { sb.append(","); final int nrFreq = key & MODEM_NR_FREQUENCY_RANGE_MASK; appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, nrFreq); } return sb.toString(); } private static void appendFieldToString(StringBuilder sb, String fieldName, SparseArray names, int key) { sb.append(fieldName); sb.append(":"); final String name = names.get(key, null); if (name == null) { sb.append("UNKNOWN(0x"); sb.append(Integer.toHexString(key)); sb.append(")"); } else { sb.append(name); } } /** * Clear this ModemPowerProfile power constants. */ public void clear() { mPowerConstants.clear(); } /** * Dump this ModemPowerProfile power constants. */ public void dump(PrintWriter pw) { final int size = mPowerConstants.size(); for (int i = 0; i < size; i++) { pw.print(keyToString(mPowerConstants.keyAt(i))); pw.print("="); pw.println(mPowerConstants.valueAt(i)); } } }