• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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