1 /* 2 * Copyright (C) 2021 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 17 package com.android.internal.power; 18 19 import android.annotation.IntDef; 20 import android.content.res.XmlResourceParser; 21 import android.telephony.ModemActivityInfo; 22 import android.telephony.ServiceState; 23 import android.telephony.TelephonyManager; 24 import android.util.Slog; 25 import android.util.SparseArray; 26 import android.util.SparseDoubleArray; 27 28 import com.android.internal.util.XmlUtils; 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 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.Arrays; 38 39 /** 40 * ModemPowerProfile for handling the modem element in the power_profile.xml 41 */ 42 public class ModemPowerProfile { 43 private static final String TAG = "ModemPowerProfile"; 44 45 private static final String TAG_SLEEP = "sleep"; 46 private static final String TAG_IDLE = "idle"; 47 private static final String TAG_ACTIVE = "active"; 48 private static final String TAG_RECEIVE = "receive"; 49 private static final String TAG_TRANSMIT = "transmit"; 50 private static final String ATTR_RAT = "rat"; 51 private static final String ATTR_NR_FREQUENCY = "nrFrequency"; 52 private static final String ATTR_LEVEL = "level"; 53 54 /** 55 * A flattened list of the modem power constant extracted from the given XML parser. 56 * 57 * The bitfields of a key describes what its corresponding power constant represents: 58 * [31:28] - {@link ModemDrainType} (max count = 16). 59 * [27:24] - {@link ModemTxLevel} (only for {@link MODEM_DRAIN_TYPE_TX}) (max count = 16). 60 * [23:20] - {@link ModemRatType} (max count = 16). 61 * [19:16] - {@link ModemNrFrequencyRange} (only for {@link MODEM_RAT_TYPE_NR}) 62 * (max count = 16). 63 * [15:0] - RESERVED 64 */ 65 private final SparseDoubleArray mPowerConstants = new SparseDoubleArray(); 66 67 private static final int MODEM_DRAIN_TYPE_MASK = 0xF000_0000; 68 private static final int MODEM_TX_LEVEL_MASK = 0x0F00_0000; 69 private static final int MODEM_RAT_TYPE_MASK = 0x00F0_0000; 70 private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0x000F_0000; 71 72 /** 73 * Corresponds to the overall modem battery drain while asleep. 74 */ 75 public static final int MODEM_DRAIN_TYPE_SLEEP = 0x0000_0000; 76 77 /** 78 * Corresponds to the overall modem battery drain while idle. 79 */ 80 public static final int MODEM_DRAIN_TYPE_IDLE = 0x1000_0000; 81 82 /** 83 * Corresponds to the modem battery drain while receiving data. A specific Rx battery drain 84 * power constant can be selected using a bitwise OR (|) with {@link ModemRatType} and 85 * {@link ModemNrFrequencyRange} (when applicable). 86 */ 87 public static final int MODEM_DRAIN_TYPE_RX = 0x2000_0000; 88 89 /** 90 * Corresponds to the modem battery drain while receiving data. 91 * {@link ModemTxLevel} must be specified with this drain type. 92 * Specific Tx battery drain power constanta can be selected using a bitwise OR (|) with 93 * {@link ModemRatType} and {@link ModemNrFrequencyRange} (when applicable). 94 */ 95 public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000; 96 97 @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = { 98 MODEM_DRAIN_TYPE_SLEEP, 99 MODEM_DRAIN_TYPE_IDLE, 100 MODEM_DRAIN_TYPE_RX, 101 MODEM_DRAIN_TYPE_TX, 102 }) 103 @Retention(RetentionPolicy.SOURCE) 104 public @interface ModemDrainType { 105 } 106 107 108 private static final SparseArray<String> MODEM_DRAIN_TYPE_NAMES = new SparseArray<>(4); 109 static { MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_SLEEP, "SLEEP")110 MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_SLEEP, "SLEEP"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_IDLE, "IDLE")111 MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_IDLE, "IDLE"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_RX, "RX")112 MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_RX, "RX"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_TX, "TX")113 MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_TX, "TX"); 114 } 115 116 /** 117 * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}. 118 */ 119 120 public static final int MODEM_TX_LEVEL_0 = 0x0000_0000; 121 122 /** 123 * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}. 124 */ 125 126 public static final int MODEM_TX_LEVEL_1 = 0x0100_0000; 127 128 /** 129 * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}. 130 */ 131 132 public static final int MODEM_TX_LEVEL_2 = 0x0200_0000; 133 134 /** 135 * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}. 136 */ 137 138 public static final int MODEM_TX_LEVEL_3 = 0x0300_0000; 139 140 /** 141 * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}. 142 */ 143 144 public static final int MODEM_TX_LEVEL_4 = 0x0400_0000; 145 146 private static final int MODEM_TX_LEVEL_COUNT = 5; 147 148 @IntDef(prefix = {"MODEM_TX_LEVEL_"}, value = { 149 MODEM_TX_LEVEL_0, 150 MODEM_TX_LEVEL_1, 151 MODEM_TX_LEVEL_2, 152 MODEM_TX_LEVEL_3, 153 MODEM_TX_LEVEL_4, 154 }) 155 @Retention(RetentionPolicy.SOURCE) 156 public @interface ModemTxLevel { 157 } 158 159 private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5); 160 static { MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0")161 MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1")162 MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2")163 MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3")164 MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3"); MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4")165 MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4"); 166 } 167 168 private static final int[] MODEM_TX_LEVEL_MAP = new int[]{ 169 MODEM_TX_LEVEL_0, 170 MODEM_TX_LEVEL_1, 171 MODEM_TX_LEVEL_2, 172 MODEM_TX_LEVEL_3, 173 MODEM_TX_LEVEL_4}; 174 175 /** 176 * Fallback for any active modem usage that does not match specified Radio Access Technology 177 * (RAT) power constants. 178 */ 179 public static final int MODEM_RAT_TYPE_DEFAULT = 0x0000_0000; 180 181 /** 182 * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT. 183 */ 184 public static final int MODEM_RAT_TYPE_LTE = 0x0010_0000; 185 186 /** 187 * Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT. 188 */ 189 public static final int MODEM_RAT_TYPE_NR = 0x0020_0000; 190 191 @IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = { 192 MODEM_RAT_TYPE_DEFAULT, 193 MODEM_RAT_TYPE_LTE, 194 MODEM_RAT_TYPE_NR, 195 }) 196 @Retention(RetentionPolicy.SOURCE) 197 public @interface ModemRatType { 198 } 199 200 private static final SparseArray<String> MODEM_RAT_TYPE_NAMES = new SparseArray<>(3); 201 static { MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_DEFAULT, "DEFAULT")202 MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_DEFAULT, "DEFAULT"); MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_LTE, "LTE")203 MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_LTE, "LTE"); MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_NR, "NR")204 MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_NR, "NR"); 205 } 206 207 /** 208 * Fallback for any active 5G modem usage that does not match specified NR frequency power 209 * constants. 210 */ 211 public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0x0000_0000; 212 213 /** 214 * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}. 215 */ 216 public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 0x0001_0000; 217 218 /** 219 * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}. 220 */ 221 public static final int MODEM_NR_FREQUENCY_RANGE_MID = 0x0002_0000; 222 223 /** 224 * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}. 225 */ 226 public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 0x0003_0000; 227 228 /** 229 * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}. 230 */ 231 public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 0x0004_0000; 232 233 @IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = { 234 MODEM_RAT_TYPE_DEFAULT, 235 MODEM_NR_FREQUENCY_RANGE_LOW, 236 MODEM_NR_FREQUENCY_RANGE_MID, 237 MODEM_NR_FREQUENCY_RANGE_HIGH, 238 MODEM_NR_FREQUENCY_RANGE_MMWAVE, 239 }) 240 @Retention(RetentionPolicy.SOURCE) 241 public @interface ModemNrFrequencyRange { 242 } 243 private static final SparseArray<String> MODEM_NR_FREQUENCY_RANGE_NAMES = new SparseArray<>(5); 244 static { MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_DEFAULT, "DEFAULT")245 MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_DEFAULT, "DEFAULT"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_LOW, "LOW")246 MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_LOW, "LOW"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MID, "MID")247 MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MID, "MID"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_HIGH, "HIGH")248 MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_HIGH, "HIGH"); MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MMWAVE, "MMWAVE")249 MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MMWAVE, "MMWAVE"); 250 } 251 ModemPowerProfile()252 public ModemPowerProfile() { 253 } 254 255 /** 256 * Generates a ModemPowerProfile object from the <modem /> element of a power_profile.xml 257 */ parseFromXml(XmlResourceParser parser)258 public void parseFromXml(XmlResourceParser parser) throws IOException, 259 XmlPullParserException { 260 final int depth = parser.getDepth(); 261 while (XmlUtils.nextElementWithin(parser, depth)) { 262 final String name = parser.getName(); 263 switch (name) { 264 case TAG_SLEEP: 265 if (parser.next() != XmlPullParser.TEXT) { 266 continue; 267 } 268 final String sleepDrain = parser.getText(); 269 setPowerConstant(MODEM_DRAIN_TYPE_SLEEP, sleepDrain); 270 break; 271 case TAG_IDLE: 272 if (parser.next() != XmlPullParser.TEXT) { 273 continue; 274 } 275 final String idleDrain = parser.getText(); 276 setPowerConstant(MODEM_DRAIN_TYPE_IDLE, idleDrain); 277 break; 278 case TAG_ACTIVE: 279 parseActivePowerConstantsFromXml(parser); 280 break; 281 default: 282 Slog.e(TAG, "Unexpected element parsed: " + name); 283 } 284 } 285 } 286 287 /** Parse the <active /> XML element */ parseActivePowerConstantsFromXml(XmlResourceParser parser)288 private void parseActivePowerConstantsFromXml(XmlResourceParser parser) 289 throws IOException, XmlPullParserException { 290 // Parse attributes to get the type of active modem usage the power constants are for. 291 final int ratType; 292 final int nrfType; 293 try { 294 ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_NAMES); 295 if (ratType == MODEM_RAT_TYPE_NR) { 296 nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY, 297 MODEM_NR_FREQUENCY_RANGE_NAMES); 298 } else { 299 nrfType = 0; 300 } 301 } catch (IllegalArgumentException iae) { 302 Slog.e(TAG, "Failed parse to active modem power constants", iae); 303 return; 304 } 305 306 // Parse and populate the active modem use power constants. 307 final int depth = parser.getDepth(); 308 while (XmlUtils.nextElementWithin(parser, depth)) { 309 final String name = parser.getName(); 310 switch (name) { 311 case TAG_RECEIVE: 312 if (parser.next() != XmlPullParser.TEXT) { 313 continue; 314 } 315 final String rxDrain = parser.getText(); 316 final int rxKey = MODEM_DRAIN_TYPE_RX | ratType | nrfType; 317 setPowerConstant(rxKey, rxDrain); 318 break; 319 case TAG_TRANSMIT: 320 final int level = XmlUtils.readIntAttribute(parser, ATTR_LEVEL, -1); 321 if (parser.next() != XmlPullParser.TEXT) { 322 continue; 323 } 324 final String txDrain = parser.getText(); 325 if (level < 0 || level >= MODEM_TX_LEVEL_COUNT) { 326 Slog.e(TAG, 327 "Unexpected tx level: " + level + ". Must be between 0 and " + ( 328 MODEM_TX_LEVEL_COUNT - 1)); 329 continue; 330 } 331 final int modemTxLevel = MODEM_TX_LEVEL_MAP[level]; 332 final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType; 333 setPowerConstant(txKey, txDrain); 334 break; 335 default: 336 Slog.e(TAG, "Unexpected element parsed: " + name); 337 } 338 } 339 } 340 getTypeFromAttribute(XmlResourceParser parser, String attr, SparseArray<String> names)341 private static int getTypeFromAttribute(XmlResourceParser parser, String attr, 342 SparseArray<String> names) { 343 final String value = XmlUtils.readStringAttribute(parser, attr); 344 if (value == null) { 345 // Attribute was not specified, just use the default. 346 return 0; 347 } 348 int index = -1; 349 final int size = names.size(); 350 // Manual linear search for string. (SparseArray uses == not equals.) 351 for (int i = 0; i < size; i++) { 352 if (value.equals(names.valueAt(i))) { 353 index = i; 354 } 355 } 356 if (index < 0) { 357 final String[] stringNames = new String[size]; 358 for (int i = 0; i < size; i++) { 359 stringNames[i] = names.valueAt(i); 360 } 361 throw new IllegalArgumentException( 362 "Unexpected " + attr + " value : " + value + ". Acceptable values are " 363 + Arrays.toString(stringNames)); 364 } 365 return names.keyAt(index); 366 } 367 368 /** 369 * Set the average battery drain in milli-amps of the modem for a given drain type. 370 * 371 * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, 372 * {@link ModemRatType}, and {@link ModemNrFrequencyRange}.key 373 * @param value the battery dram in milli-amps for the given key. 374 */ setPowerConstant(int key, String value)375 public void setPowerConstant(int key, String value) { 376 try { 377 mPowerConstants.put(key, Double.valueOf(value)); 378 } catch (Exception e) { 379 Slog.e(TAG, "Failed to set power constant 0x" + Integer.toHexString( 380 key) + "(" + keyToString(key) + ") to " + value, e); 381 } 382 } 383 384 /** 385 * Returns the average battery drain in milli-amps of the modem for a given drain type. 386 * Returns {@link Double.NaN} if a suitable value is not found for the given key. 387 * 388 * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel}, 389 * {@link ModemRatType}, and {@link ModemNrFrequencyRange}. 390 */ getAverageBatteryDrainMa(int key)391 public double getAverageBatteryDrainMa(int key) { 392 int bestKey = key; 393 double value; 394 value = mPowerConstants.get(bestKey, Double.NaN); 395 if (!Double.isNaN(value)) return value; 396 // The power constant for given key was not explicitly set. Try to fallback to possible 397 // defaults. 398 399 if ((bestKey & MODEM_NR_FREQUENCY_RANGE_MASK) != MODEM_NR_FREQUENCY_RANGE_DEFAULT) { 400 // Fallback to NR Frequency default value 401 bestKey &= ~MODEM_NR_FREQUENCY_RANGE_MASK; 402 bestKey |= MODEM_NR_FREQUENCY_RANGE_DEFAULT; 403 value = mPowerConstants.get(bestKey, Double.NaN); 404 if (!Double.isNaN(value)) return value; 405 } 406 407 if ((bestKey & MODEM_RAT_TYPE_MASK) != MODEM_RAT_TYPE_DEFAULT) { 408 // Fallback to RAT default value 409 bestKey &= ~MODEM_RAT_TYPE_MASK; 410 bestKey |= MODEM_RAT_TYPE_DEFAULT; 411 value = mPowerConstants.get(bestKey, Double.NaN); 412 if (!Double.isNaN(value)) return value; 413 } 414 415 Slog.w(TAG, 416 "getAverageBatteryDrainMaH called with unexpected key: 0x" + Integer.toHexString( 417 key) + ", " + keyToString(key)); 418 return Double.NaN; 419 } 420 keyToString(int key)421 private static String keyToString(int key) { 422 StringBuilder sb = new StringBuilder(); 423 final int drainType = key & MODEM_DRAIN_TYPE_MASK; 424 appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType); 425 sb.append(","); 426 427 if (drainType == MODEM_DRAIN_TYPE_TX) { 428 final int txLevel = key & MODEM_TX_LEVEL_MASK; 429 appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel); 430 } 431 432 final int ratType = key & MODEM_RAT_TYPE_MASK; 433 appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType); 434 435 if (ratType == MODEM_RAT_TYPE_NR) { 436 sb.append(","); 437 final int nrFreq = key & MODEM_NR_FREQUENCY_RANGE_MASK; 438 appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, nrFreq); 439 } 440 return sb.toString(); 441 } appendFieldToString(StringBuilder sb, String fieldName, SparseArray<String> names, int key)442 private static void appendFieldToString(StringBuilder sb, String fieldName, 443 SparseArray<String> names, int key) { 444 sb.append(fieldName); 445 sb.append(":"); 446 final String name = names.get(key, null); 447 if (name == null) { 448 sb.append("UNKNOWN(0x"); 449 sb.append(Integer.toHexString(key)); 450 sb.append(")"); 451 } else { 452 sb.append(name); 453 } 454 } 455 456 /** 457 * Clear this ModemPowerProfile power constants. 458 */ clear()459 public void clear() { 460 mPowerConstants.clear(); 461 } 462 463 464 /** 465 * Dump this ModemPowerProfile power constants. 466 */ dump(PrintWriter pw)467 public void dump(PrintWriter pw) { 468 final int size = mPowerConstants.size(); 469 for (int i = 0; i < size; i++) { 470 pw.print(keyToString(mPowerConstants.keyAt(i))); 471 pw.print("="); 472 pw.println(mPowerConstants.valueAt(i)); 473 } 474 } 475 } 476