1 /*
2  * Copyright (C) 2015 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 package com.android.internal.os;
17 
18 import android.os.BatteryConsumer;
19 import android.os.BatteryStats;
20 import android.os.BatteryStats.ControllerActivityCounter;
21 import android.os.BatteryUsageStats;
22 import android.os.BatteryUsageStatsQuery;
23 import android.os.Process;
24 import android.os.UidBatteryConsumer;
25 import android.os.UserHandle;
26 import android.util.Log;
27 import android.util.SparseArray;
28 
29 import java.util.List;
30 
31 public class BluetoothPowerCalculator extends PowerCalculator {
32     private static final String TAG = "BluetoothPowerCalc";
33     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
34     private final double mIdleMa;
35     private final double mRxMa;
36     private final double mTxMa;
37     private final boolean mHasBluetoothPowerController;
38 
39     private static class PowerAndDuration {
40         public long durationMs;
41         public double powerMah;
42     }
43 
BluetoothPowerCalculator(PowerProfile profile)44     public BluetoothPowerCalculator(PowerProfile profile) {
45         mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE);
46         mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX);
47         mTxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX);
48         mHasBluetoothPowerController = mIdleMa != 0 && mRxMa != 0 && mTxMa != 0;
49     }
50 
51     @Override
calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query)52     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
53             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
54         if (!batteryStats.hasBluetoothActivityReporting()) {
55             return;
56         }
57 
58         final PowerAndDuration total = new PowerAndDuration();
59 
60         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
61                 builder.getUidBatteryConsumerBuilders();
62         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
63             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
64             calculateApp(app, total, query);
65         }
66 
67         final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
68         final int powerModel = getPowerModel(measuredChargeUC, query);
69         final ControllerActivityCounter activityCounter =
70                 batteryStats.getBluetoothControllerActivity();
71         final long systemDurationMs = calculateDuration(activityCounter);
72         final double systemPowerMah = calculatePowerMah(powerModel, measuredChargeUC,
73                 activityCounter, query.shouldForceUsePowerProfileModel());
74 
75         // Subtract what the apps used, but clamp to 0.
76         final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs);
77         if (DEBUG) {
78             Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs)
79                     + " power=" + formatCharge(systemPowerMah));
80         }
81 
82         builder.getAggregateBatteryConsumerBuilder(
83                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
84                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, systemDurationMs)
85                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
86                         Math.max(systemPowerMah, total.powerMah), powerModel);
87 
88         builder.getAggregateBatteryConsumerBuilder(
89                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
90                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.durationMs)
91                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.powerMah,
92                         powerModel);
93     }
94 
calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total, BatteryUsageStatsQuery query)95     private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
96             BatteryUsageStatsQuery query) {
97         final long measuredChargeUC =
98                 app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC();
99         final int powerModel = getPowerModel(measuredChargeUC, query);
100         final ControllerActivityCounter activityCounter =
101                 app.getBatteryStatsUid().getBluetoothControllerActivity();
102         final long durationMs = calculateDuration(activityCounter);
103         final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter,
104                 query.shouldForceUsePowerProfileModel());
105 
106         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, durationMs)
107                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah, powerModel);
108 
109         total.durationMs += durationMs;
110         total.powerMah += powerMah;
111     }
112 
113     @Override
calculate(List<BatterySipper> sippers, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers)114     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
115             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
116         if (!mHasBluetoothPowerController || !batteryStats.hasBluetoothActivityReporting()) {
117             return;
118         }
119 
120         PowerAndDuration total = new PowerAndDuration();
121 
122         for (int i = sippers.size() - 1; i >= 0; i--) {
123             final BatterySipper app = sippers.get(i);
124             if (app.drainType == BatterySipper.DrainType.APP) {
125                 calculateApp(app, app.uidObj, statsType, total);
126             }
127         }
128 
129         BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
130         final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
131         final int powerModel = getPowerModel(measuredChargeUC);
132         final ControllerActivityCounter activityCounter =
133                 batteryStats.getBluetoothControllerActivity();
134         final long systemDurationMs = calculateDuration(activityCounter);
135         final double systemPowerMah =
136                 calculatePowerMah(powerModel, measuredChargeUC, activityCounter, false);
137 
138         // Subtract what the apps used, but clamp to 0.
139         final double powerMah = Math.max(0, systemPowerMah - total.powerMah);
140         final long durationMs = Math.max(0, systemDurationMs - total.durationMs);
141         if (DEBUG && powerMah != 0) {
142             Log.d(TAG, "Bluetooth active: time=" + (durationMs)
143                     + " power=" + formatCharge(powerMah));
144         }
145 
146         bs.bluetoothPowerMah = powerMah;
147         bs.bluetoothRunningTimeMs = durationMs;
148 
149         for (int i = sippers.size() - 1; i >= 0; i--) {
150             BatterySipper app = sippers.get(i);
151             if (app.getUid() == Process.BLUETOOTH_UID) {
152                 if (DEBUG) Log.d(TAG, "Bluetooth adding sipper " + app + ": cpu=" + app.cpuTimeMs);
153                 app.isAggregated = true;
154                 bs.add(app);
155             }
156         }
157         if (bs.sumPower() > 0) {
158             sippers.add(bs);
159         }
160     }
161 
calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, PowerAndDuration total)162     private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
163             PowerAndDuration total) {
164 
165         final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC();
166         final int powerModel = getPowerModel(measuredChargeUC);
167         final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity();
168         final long durationMs = calculateDuration(activityCounter);
169         final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter,
170                 false);
171 
172         app.bluetoothRunningTimeMs = durationMs;
173         app.bluetoothPowerMah = powerMah;
174         app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType);
175         app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType);
176 
177         total.durationMs += durationMs;
178         total.powerMah += powerMah;
179     }
180 
calculateDuration(ControllerActivityCounter counter)181     private long calculateDuration(ControllerActivityCounter counter) {
182         if (counter == null) {
183             return 0;
184         }
185 
186         return counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
187                 + counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
188                 + counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
189     }
190 
191     /** Returns bluetooth power usage based on the best data available. */
calculatePowerMah(@atteryConsumer.PowerModel int powerModel, long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower)192     private double calculatePowerMah(@BatteryConsumer.PowerModel int powerModel,
193             long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower) {
194         if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
195             return uCtoMah(measuredChargeUC);
196         }
197 
198         if (counter == null) {
199             return 0;
200         }
201 
202         if (!ignoreReportedPower) {
203             final double powerMah =
204                     counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
205                             / (double) (1000 * 60 * 60);
206             if (powerMah != 0) {
207                 return powerMah;
208             }
209         }
210 
211         if (!mHasBluetoothPowerController) {
212             return 0;
213         }
214 
215         final long idleTimeMs =
216                 counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
217         final long rxTimeMs =
218                 counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
219         final long txTimeMs =
220                 counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
221         return calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
222     }
223 
224     /** Returns estimated bluetooth power usage based on usage times. */
calculatePowerMah(long rxTimeMs, long txTimeMs, long idleTimeMs)225     public double calculatePowerMah(long rxTimeMs, long txTimeMs, long idleTimeMs) {
226         return ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
227                 / (1000 * 60 * 60);
228     }
229 }
230