1 /*
2  * Copyright (C) 2009 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.os;
18 
19 
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.content.res.XmlResourceParser;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.internal.util.XmlUtils;
26 
27 import org.xmlpull.v1.XmlPullParser;
28 import org.xmlpull.v1.XmlPullParserException;
29 
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 
34 /**
35  * Reports power consumption values for various device activities. Reads values from an XML file.
36  * Customize the XML file for different devices.
37  * [hidden]
38  */
39 public class PowerProfile {
40 
41     /*
42      * POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode.
43      * POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should
44      *                 be zero on devices that can go into full CPU power collapse even when a wake
45      *                 lock is held. Otherwise, this is the power consumption in addition to
46      * POWER_CPU_SUSPEND due to a wake lock being held but with no CPU activity.
47      * POWER_CPU_ACTIVE: Power consumption when CPU is running, excluding power consumed by clusters
48      *                   and cores.
49      *
50      * CPU Power Equation (assume two clusters):
51      * Total power = POWER_CPU_SUSPEND  (always added)
52      *               + POWER_CPU_IDLE   (skip this and below if in power collapse mode)
53      *               + POWER_CPU_ACTIVE (skip this and below if CPU is not running, but a wakelock
54      *                                   is held)
55      *               + cluster_power.cluster0 + cluster_power.cluster1 (skip cluster not running)
56      *               + core_power.cluster0 * num running cores in cluster 0
57      *               + core_power.cluster1 * num running cores in cluster 1
58      */
59     public static final String POWER_CPU_SUSPEND = "cpu.suspend";
60     public static final String POWER_CPU_IDLE = "cpu.idle";
61     public static final String POWER_CPU_ACTIVE = "cpu.active";
62 
63     /**
64      * Power consumption when WiFi driver is scanning for networks.
65      */
66     public static final String POWER_WIFI_SCAN = "wifi.scan";
67 
68     /**
69      * Power consumption when WiFi driver is on.
70      */
71     public static final String POWER_WIFI_ON = "wifi.on";
72 
73     /**
74      * Power consumption when WiFi driver is transmitting/receiving.
75      */
76     public static final String POWER_WIFI_ACTIVE = "wifi.active";
77 
78     //
79     // Updated power constants. These are not estimated, they are real world
80     // currents and voltages for the underlying bluetooth and wifi controllers.
81     //
82     public static final String POWER_WIFI_CONTROLLER_IDLE = "wifi.controller.idle";
83     public static final String POWER_WIFI_CONTROLLER_RX = "wifi.controller.rx";
84     public static final String POWER_WIFI_CONTROLLER_TX = "wifi.controller.tx";
85     public static final String POWER_WIFI_CONTROLLER_TX_LEVELS = "wifi.controller.tx_levels";
86     public static final String POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE = "wifi.controller.voltage";
87 
88     public static final String POWER_BLUETOOTH_CONTROLLER_IDLE = "bluetooth.controller.idle";
89     public static final String POWER_BLUETOOTH_CONTROLLER_RX = "bluetooth.controller.rx";
90     public static final String POWER_BLUETOOTH_CONTROLLER_TX = "bluetooth.controller.tx";
91     public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE =
92             "bluetooth.controller.voltage";
93 
94     public static final String POWER_MODEM_CONTROLLER_SLEEP = "modem.controller.sleep";
95     public static final String POWER_MODEM_CONTROLLER_IDLE = "modem.controller.idle";
96     public static final String POWER_MODEM_CONTROLLER_RX = "modem.controller.rx";
97     public static final String POWER_MODEM_CONTROLLER_TX = "modem.controller.tx";
98     public static final String POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE =
99             "modem.controller.voltage";
100 
101     /**
102      * Power consumption when GPS is on.
103      */
104     public static final String POWER_GPS_ON = "gps.on";
105 
106     /**
107      * GPS power parameters based on signal quality
108      */
109     public static final String POWER_GPS_SIGNAL_QUALITY_BASED = "gps.signalqualitybased";
110     public static final String POWER_GPS_OPERATING_VOLTAGE = "gps.voltage";
111 
112     /**
113      * Power consumption when Bluetooth driver is on.
114      *
115      * @deprecated
116      */
117     @Deprecated
118     public static final String POWER_BLUETOOTH_ON = "bluetooth.on";
119 
120     /**
121      * Power consumption when Bluetooth driver is transmitting/receiving.
122      *
123      * @deprecated
124      */
125     @Deprecated
126     public static final String POWER_BLUETOOTH_ACTIVE = "bluetooth.active";
127 
128     /**
129      * Power consumption when Bluetooth driver gets an AT command.
130      *
131      * @deprecated
132      */
133     @Deprecated
134     public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
135 
136     /**
137      * Power consumption when screen is in doze/ambient/always-on mode, including backlight power.
138      */
139     public static final String POWER_AMBIENT_DISPLAY = "ambient.on";
140 
141     /**
142      * Power consumption when screen is on, not including the backlight power.
143      */
144     public static final String POWER_SCREEN_ON = "screen.on";
145 
146     /**
147      * Power consumption when cell radio is on but not on a call.
148      */
149     public static final String POWER_RADIO_ON = "radio.on";
150 
151     /**
152      * Power consumption when cell radio is hunting for a signal.
153      */
154     public static final String POWER_RADIO_SCANNING = "radio.scanning";
155 
156     /**
157      * Power consumption when talking on the phone.
158      */
159     public static final String POWER_RADIO_ACTIVE = "radio.active";
160 
161     /**
162      * Power consumption at full backlight brightness. If the backlight is at
163      * 50% brightness, then this should be multiplied by 0.5
164      */
165     public static final String POWER_SCREEN_FULL = "screen.full";
166 
167     /**
168      * Power consumed by the audio hardware when playing back audio content. This is in addition
169      * to the CPU power, probably due to a DSP and / or amplifier.
170      */
171     public static final String POWER_AUDIO = "audio";
172 
173     /**
174      * Power consumed by any media hardware when playing back video content. This is in addition
175      * to the CPU power, probably due to a DSP.
176      */
177     public static final String POWER_VIDEO = "video";
178 
179     /**
180      * Average power consumption when camera flashlight is on.
181      */
182     public static final String POWER_FLASHLIGHT = "camera.flashlight";
183 
184     /**
185      * Power consumption when DDR is being used.
186      */
187     public static final String POWER_MEMORY = "memory.bandwidths";
188 
189     /**
190      * Average power consumption when the camera is on over all standard use cases.
191      *
192      * TODO: Add more fine-grained camera power metrics.
193      */
194     public static final String POWER_CAMERA = "camera.avg";
195 
196     /**
197      * Power consumed by wif batched scaning.  Broken down into bins by
198      * Channels Scanned per Hour.  May do 1-720 scans per hour of 1-100 channels
199      * for a range of 1-72,000.  Going logrithmic (1-8, 9-64, 65-512, 513-4096, 4097-)!
200      */
201     public static final String POWER_WIFI_BATCHED_SCAN = "wifi.batchedscan";
202 
203     /**
204      * Battery capacity in milliAmpHour (mAh).
205      */
206     public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
207 
208     /**
209      * A map from Power Use Item to its power consumption.
210      */
211     static final HashMap<String, Double> sPowerItemMap = new HashMap<>();
212     /**
213      * A map from Power Use Item to an array of its power consumption
214      * (for items with variable power e.g. CPU).
215      */
216     static final HashMap<String, Double[]> sPowerArrayMap = new HashMap<>();
217 
218     private static final String TAG_DEVICE = "device";
219     private static final String TAG_ITEM = "item";
220     private static final String TAG_ARRAY = "array";
221     private static final String TAG_ARRAYITEM = "value";
222     private static final String ATTR_NAME = "name";
223 
224     private static final Object sLock = new Object();
225 
226     @VisibleForTesting
PowerProfile(Context context)227     public PowerProfile(Context context) {
228         this(context, false);
229     }
230 
231     /**
232      * For PowerProfileTest
233      */
234     @VisibleForTesting
PowerProfile(Context context, boolean forTest)235     public PowerProfile(Context context, boolean forTest) {
236         // Read the XML file for the given profile (normally only one per device)
237         synchronized (sLock) {
238             if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) {
239                 readPowerValuesFromXml(context, forTest);
240             }
241             initCpuClusters();
242         }
243     }
244 
readPowerValuesFromXml(Context context, boolean forTest)245     private void readPowerValuesFromXml(Context context, boolean forTest) {
246         final int id = forTest ? com.android.internal.R.xml.power_profile_test :
247                 com.android.internal.R.xml.power_profile;
248         final Resources resources = context.getResources();
249         XmlResourceParser parser = resources.getXml(id);
250         boolean parsingArray = false;
251         ArrayList<Double> array = new ArrayList<>();
252         String arrayName = null;
253 
254         try {
255             XmlUtils.beginDocument(parser, TAG_DEVICE);
256 
257             while (true) {
258                 XmlUtils.nextElement(parser);
259 
260                 String element = parser.getName();
261                 if (element == null) break;
262 
263                 if (parsingArray && !element.equals(TAG_ARRAYITEM)) {
264                     // Finish array
265                     sPowerArrayMap.put(arrayName, array.toArray(new Double[array.size()]));
266                     parsingArray = false;
267                 }
268                 if (element.equals(TAG_ARRAY)) {
269                     parsingArray = true;
270                     array.clear();
271                     arrayName = parser.getAttributeValue(null, ATTR_NAME);
272                 } else if (element.equals(TAG_ITEM) || element.equals(TAG_ARRAYITEM)) {
273                     String name = null;
274                     if (!parsingArray) name = parser.getAttributeValue(null, ATTR_NAME);
275                     if (parser.next() == XmlPullParser.TEXT) {
276                         String power = parser.getText();
277                         double value = 0;
278                         try {
279                             value = Double.valueOf(power);
280                         } catch (NumberFormatException nfe) {
281                         }
282                         if (element.equals(TAG_ITEM)) {
283                             sPowerItemMap.put(name, value);
284                         } else if (parsingArray) {
285                             array.add(value);
286                         }
287                     }
288                 }
289             }
290             if (parsingArray) {
291                 sPowerArrayMap.put(arrayName, array.toArray(new Double[array.size()]));
292             }
293         } catch (XmlPullParserException e) {
294             throw new RuntimeException(e);
295         } catch (IOException e) {
296             throw new RuntimeException(e);
297         } finally {
298             parser.close();
299         }
300 
301         // Now collect other config variables.
302         int[] configResIds = new int[]{
303                 com.android.internal.R.integer.config_bluetooth_idle_cur_ma,
304                 com.android.internal.R.integer.config_bluetooth_rx_cur_ma,
305                 com.android.internal.R.integer.config_bluetooth_tx_cur_ma,
306                 com.android.internal.R.integer.config_bluetooth_operating_voltage_mv,
307         };
308 
309         String[] configResIdKeys = new String[]{
310                 POWER_BLUETOOTH_CONTROLLER_IDLE,
311                 POWER_BLUETOOTH_CONTROLLER_RX,
312                 POWER_BLUETOOTH_CONTROLLER_TX,
313                 POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE,
314         };
315 
316         for (int i = 0; i < configResIds.length; i++) {
317             String key = configResIdKeys[i];
318             // if we already have some of these parameters in power_profile.xml, ignore the
319             // value in config.xml
320             if ((sPowerItemMap.containsKey(key) && sPowerItemMap.get(key) > 0)) {
321                 continue;
322             }
323             int value = resources.getInteger(configResIds[i]);
324             if (value > 0) {
325                 sPowerItemMap.put(key, (double) value);
326             }
327         }
328     }
329 
330     private CpuClusterKey[] mCpuClusters;
331 
332     private static final String CPU_PER_CLUSTER_CORE_COUNT = "cpu.clusters.cores";
333     private static final String CPU_CLUSTER_POWER_COUNT = "cpu.cluster_power.cluster";
334     private static final String CPU_CORE_SPEED_PREFIX = "cpu.core_speeds.cluster";
335     private static final String CPU_CORE_POWER_PREFIX = "cpu.core_power.cluster";
336 
initCpuClusters()337     private void initCpuClusters() {
338         if (sPowerArrayMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) {
339             final Double[] data = sPowerArrayMap.get(CPU_PER_CLUSTER_CORE_COUNT);
340             mCpuClusters = new CpuClusterKey[data.length];
341             for (int cluster = 0; cluster < data.length; cluster++) {
342                 int numCpusInCluster = (int) Math.round(data[cluster]);
343                 mCpuClusters[cluster] = new CpuClusterKey(
344                         CPU_CORE_SPEED_PREFIX + cluster, CPU_CLUSTER_POWER_COUNT + cluster,
345                         CPU_CORE_POWER_PREFIX + cluster, numCpusInCluster);
346             }
347         } else {
348             // Default to single.
349             mCpuClusters = new CpuClusterKey[1];
350             int numCpus = 1;
351             if (sPowerItemMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) {
352                 numCpus = (int) Math.round(sPowerItemMap.get(CPU_PER_CLUSTER_CORE_COUNT));
353             }
354             mCpuClusters[0] = new CpuClusterKey(CPU_CORE_SPEED_PREFIX + 0,
355                     CPU_CLUSTER_POWER_COUNT + 0, CPU_CORE_POWER_PREFIX + 0, numCpus);
356         }
357     }
358 
359     public static class CpuClusterKey {
360         private final String freqKey;
361         private final String clusterPowerKey;
362         private final String corePowerKey;
363         private final int numCpus;
364 
CpuClusterKey(String freqKey, String clusterPowerKey, String corePowerKey, int numCpus)365         private CpuClusterKey(String freqKey, String clusterPowerKey,
366                 String corePowerKey, int numCpus) {
367             this.freqKey = freqKey;
368             this.clusterPowerKey = clusterPowerKey;
369             this.corePowerKey = corePowerKey;
370             this.numCpus = numCpus;
371         }
372     }
373 
getNumCpuClusters()374     public int getNumCpuClusters() {
375         return mCpuClusters.length;
376     }
377 
getNumCoresInCpuCluster(int cluster)378     public int getNumCoresInCpuCluster(int cluster) {
379         return mCpuClusters[cluster].numCpus;
380     }
381 
getNumSpeedStepsInCpuCluster(int cluster)382     public int getNumSpeedStepsInCpuCluster(int cluster) {
383         if (cluster < 0 || cluster >= mCpuClusters.length) {
384             return 0; // index out of bound
385         }
386         if (sPowerArrayMap.containsKey(mCpuClusters[cluster].freqKey)) {
387             return sPowerArrayMap.get(mCpuClusters[cluster].freqKey).length;
388         }
389         return 1; // Only one speed
390     }
391 
getAveragePowerForCpuCluster(int cluster)392     public double getAveragePowerForCpuCluster(int cluster) {
393         if (cluster >= 0 && cluster < mCpuClusters.length) {
394             return getAveragePower(mCpuClusters[cluster].clusterPowerKey);
395         }
396         return 0;
397     }
398 
getAveragePowerForCpuCore(int cluster, int step)399     public double getAveragePowerForCpuCore(int cluster, int step) {
400         if (cluster >= 0 && cluster < mCpuClusters.length) {
401             return getAveragePower(mCpuClusters[cluster].corePowerKey, step);
402         }
403         return 0;
404     }
405 
406     /**
407      * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a
408      * default value if the subsystem has no recorded value.
409      *
410      * @return the number of memory bandwidth buckets.
411      */
getNumElements(String key)412     public int getNumElements(String key) {
413         if (sPowerItemMap.containsKey(key)) {
414             return 1;
415         } else if (sPowerArrayMap.containsKey(key)) {
416             return sPowerArrayMap.get(key).length;
417         }
418         return 0;
419     }
420 
421     /**
422      * Returns the average current in mA consumed by the subsystem, or the given
423      * default value if the subsystem has no recorded value.
424      *
425      * @param type         the subsystem type
426      * @param defaultValue the value to return if the subsystem has no recorded value.
427      * @return the average current in milliAmps.
428      */
getAveragePowerOrDefault(String type, double defaultValue)429     public double getAveragePowerOrDefault(String type, double defaultValue) {
430         if (sPowerItemMap.containsKey(type)) {
431             return sPowerItemMap.get(type);
432         } else if (sPowerArrayMap.containsKey(type)) {
433             return sPowerArrayMap.get(type)[0];
434         } else {
435             return defaultValue;
436         }
437     }
438 
439     /**
440      * Returns the average current in mA consumed by the subsystem
441      *
442      * @param type the subsystem type
443      * @return the average current in milliAmps.
444      */
getAveragePower(String type)445     public double getAveragePower(String type) {
446         return getAveragePowerOrDefault(type, 0);
447     }
448 
449     /**
450      * Returns the average current in mA consumed by the subsystem for the given level.
451      *
452      * @param type  the subsystem type
453      * @param level the level of power at which the subsystem is running. For instance, the
454      *              signal strength of the cell network between 0 and 4 (if there are 4 bars max.)
455      *              If there is no data for multiple levels, the level is ignored.
456      * @return the average current in milliAmps.
457      */
getAveragePower(String type, int level)458     public double getAveragePower(String type, int level) {
459         if (sPowerItemMap.containsKey(type)) {
460             return sPowerItemMap.get(type);
461         } else if (sPowerArrayMap.containsKey(type)) {
462             final Double[] values = sPowerArrayMap.get(type);
463             if (values.length > level && level >= 0) {
464                 return values[level];
465             } else if (level < 0 || values.length == 0) {
466                 return 0;
467             } else {
468                 return values[values.length - 1];
469             }
470         } else {
471             return 0;
472         }
473     }
474 
475     /**
476      * Returns the battery capacity, if available, in milli Amp Hours. If not available,
477      * it returns zero.
478      *
479      * @return the battery capacity in mAh
480      */
getBatteryCapacity()481     public double getBatteryCapacity() {
482         return getAveragePower(POWER_BATTERY_CAPACITY);
483     }
484 }
485