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