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
17package com.android.internal.os;
18
19
20import android.content.Context;
21import android.content.res.Resources;
22import android.content.res.XmlResourceParser;
23
24import com.android.internal.util.XmlUtils;
25
26import org.xmlpull.v1.XmlPullParser;
27import org.xmlpull.v1.XmlPullParserException;
28
29import java.io.IOException;
30import java.util.ArrayList;
31import 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 */
38public 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
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
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")
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
342        private CpuClusterKey(String timeKey, String powerKey, int numCpus) {
343            this.timeKey = timeKey;
344            this.powerKey = powerKey;
345            this.numCpus = numCpus;
346        }
347    }
348
349    public int getNumCpuClusters() {
350        return mCpuClusters.length;
351    }
352
353    public int getNumCoresInCpuCluster(int index) {
354        return mCpuClusters[index].numCpus;
355    }
356
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
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     */
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     */
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     */
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     */
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     */
452    public double getBatteryCapacity() {
453        return getAveragePower(POWER_BATTERY_CAPACITY);
454    }
455}
456