1 /*
2  * Copyright (C) 2024 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.server.power.stats;
17 
18 import android.bluetooth.BluetoothActivityEnergyInfo;
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.UidTraffic;
21 import android.content.pm.PackageManager;
22 import android.hardware.power.stats.EnergyConsumerType;
23 import android.os.BatteryConsumer;
24 import android.os.Handler;
25 import android.os.PersistableBundle;
26 import android.util.Slog;
27 import android.util.SparseArray;
28 
29 import com.android.internal.os.Clock;
30 import com.android.internal.os.PowerStats;
31 
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.concurrent.CompletableFuture;
35 import java.util.concurrent.Executor;
36 import java.util.concurrent.TimeUnit;
37 import java.util.function.IntSupplier;
38 
39 public class BluetoothPowerStatsCollector extends PowerStatsCollector {
40     private static final String TAG = "BluetoothPowerStatsCollector";
41 
42     private static final long BLUETOOTH_ACTIVITY_REQUEST_TIMEOUT = 20000;
43 
44     private static final long ENERGY_UNSPECIFIED = -1;
45 
46     interface BluetoothStatsRetriever {
47         interface Callback {
onBluetoothScanTime(int uid, long scanTimeMs)48             void onBluetoothScanTime(int uid, long scanTimeMs);
49         }
50 
retrieveBluetoothScanTimes(Callback callback)51         void retrieveBluetoothScanTimes(Callback callback);
52 
requestControllerActivityEnergyInfo(Executor executor, BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback)53         boolean requestControllerActivityEnergyInfo(Executor executor,
54                 BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback);
55     }
56 
57     interface Injector {
getHandler()58         Handler getHandler();
getClock()59         Clock getClock();
getUidResolver()60         PowerStatsUidResolver getUidResolver();
getPowerStatsCollectionThrottlePeriod(String powerComponentName)61         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
getPackageManager()62         PackageManager getPackageManager();
getConsumedEnergyRetriever()63         ConsumedEnergyRetriever getConsumedEnergyRetriever();
getVoltageSupplier()64         IntSupplier getVoltageSupplier();
getBluetoothStatsRetriever()65         BluetoothStatsRetriever getBluetoothStatsRetriever();
66     }
67 
68     private final Injector mInjector;
69 
70     private BluetoothPowerStatsLayout mLayout;
71     private boolean mIsInitialized;
72     private PowerStats mPowerStats;
73     private long[] mDeviceStats;
74     private BluetoothStatsRetriever mBluetoothStatsRetriever;
75     private ConsumedEnergyRetriever mConsumedEnergyRetriever;
76     private IntSupplier mVoltageSupplier;
77     private int[] mEnergyConsumerIds = new int[0];
78     private long[] mLastConsumedEnergyUws;
79     private int mLastVoltageMv;
80 
81     private long mLastRxTime;
82     private long mLastTxTime;
83     private long mLastIdleTime;
84 
85     private static class UidStats {
86         public long rxCount;
87         public long lastRxCount;
88         public long txCount;
89         public long lastTxCount;
90         public long scanTime;
91         public long lastScanTime;
92     }
93 
94     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
95 
BluetoothPowerStatsCollector(Injector injector)96     BluetoothPowerStatsCollector(Injector injector) {
97         super(injector.getHandler(),  injector.getPowerStatsCollectionThrottlePeriod(
98                         BatteryConsumer.powerComponentIdToString(
99                                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH)),
100                 injector.getUidResolver(),
101                 injector.getClock());
102         mInjector = injector;
103     }
104 
105     @Override
setEnabled(boolean enabled)106     public void setEnabled(boolean enabled) {
107         if (enabled) {
108             PackageManager packageManager = mInjector.getPackageManager();
109             super.setEnabled(packageManager != null
110                     && packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH));
111         } else {
112             super.setEnabled(false);
113         }
114     }
115 
ensureInitialized()116     private boolean ensureInitialized() {
117         if (mIsInitialized) {
118             return true;
119         }
120 
121         if (!isEnabled()) {
122             return false;
123         }
124 
125         mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
126         mVoltageSupplier = mInjector.getVoltageSupplier();
127         mBluetoothStatsRetriever = mInjector.getBluetoothStatsRetriever();
128         mEnergyConsumerIds =
129                 mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH);
130         mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
131         Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
132 
133         mLayout = new BluetoothPowerStatsLayout();
134         mLayout.addDeviceBluetoothControllerActivity();
135         mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
136         mLayout.addDeviceSectionUsageDuration();
137         mLayout.addDeviceSectionPowerEstimate();
138         mLayout.addUidTrafficStats();
139         mLayout.addUidSectionPowerEstimate();
140 
141         PersistableBundle extras = new PersistableBundle();
142         mLayout.toExtras(extras);
143         PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
144                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH, mLayout.getDeviceStatsArrayLength(),
145                 null, 0, mLayout.getUidStatsArrayLength(),
146                 extras);
147         mPowerStats = new PowerStats(powerStatsDescriptor);
148         mDeviceStats = mPowerStats.stats;
149 
150         mIsInitialized = true;
151         return true;
152     }
153 
154     @Override
collectStats()155     protected PowerStats collectStats() {
156         if (!ensureInitialized()) {
157             return null;
158         }
159 
160         mPowerStats.uidStats.clear();
161 
162         collectBluetoothActivityInfo();
163         collectBluetoothScanStats();
164 
165         if (mEnergyConsumerIds.length != 0) {
166             collectEnergyConsumers();
167         }
168 
169         return mPowerStats;
170     }
171 
collectBluetoothActivityInfo()172     private void collectBluetoothActivityInfo() {
173         CompletableFuture<BluetoothActivityEnergyInfo> immediateFuture = new CompletableFuture<>();
174         boolean success = mBluetoothStatsRetriever.requestControllerActivityEnergyInfo(
175                 Runnable::run,
176                 new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() {
177                     @Override
178                     public void onBluetoothActivityEnergyInfoAvailable(
179                             BluetoothActivityEnergyInfo info) {
180                         immediateFuture.complete(info);
181                     }
182 
183                     @Override
184                     public void onBluetoothActivityEnergyInfoError(int error) {
185                         immediateFuture.completeExceptionally(
186                                 new RuntimeException("error: " + error));
187                     }
188                 });
189 
190         if (!success) {
191             return;
192         }
193 
194         BluetoothActivityEnergyInfo activityInfo;
195         try {
196             activityInfo = immediateFuture.get(BLUETOOTH_ACTIVITY_REQUEST_TIMEOUT,
197                     TimeUnit.MILLISECONDS);
198         } catch (Exception e) {
199             Slog.e(TAG, "Cannot acquire BluetoothActivityEnergyInfo", e);
200             activityInfo = null;
201         }
202 
203         if (activityInfo == null) {
204             return;
205         }
206 
207         long rxTime = activityInfo.getControllerRxTimeMillis();
208         long rxTimeDelta = Math.max(0, rxTime - mLastRxTime);
209         mLayout.setDeviceRxTime(mDeviceStats, rxTimeDelta);
210         mLastRxTime = rxTime;
211 
212         long txTime = activityInfo.getControllerTxTimeMillis();
213         long txTimeDelta = Math.max(0, txTime - mLastTxTime);
214         mLayout.setDeviceTxTime(mDeviceStats, txTimeDelta);
215         mLastTxTime = txTime;
216 
217         long idleTime = activityInfo.getControllerIdleTimeMillis();
218         long idleTimeDelta = Math.max(0, idleTime - mLastIdleTime);
219         mLayout.setDeviceIdleTime(mDeviceStats, idleTimeDelta);
220         mLastIdleTime = idleTime;
221 
222         mPowerStats.durationMs = rxTimeDelta + txTimeDelta + idleTimeDelta;
223 
224         List<UidTraffic> uidTraffic = activityInfo.getUidTraffic();
225         for (int i = uidTraffic.size() - 1; i >= 0; i--) {
226             UidTraffic ut = uidTraffic.get(i);
227             int uid = mUidResolver.mapUid(ut.getUid());
228             UidStats counts = mUidStats.get(uid);
229             if (counts == null) {
230                 counts = new UidStats();
231                 mUidStats.put(uid, counts);
232             }
233             counts.rxCount += ut.getRxBytes();
234             counts.txCount += ut.getTxBytes();
235         }
236 
237         for (int i = mUidStats.size() - 1; i >= 0; i--) {
238             UidStats counts = mUidStats.valueAt(i);
239             long rxDelta = Math.max(0, counts.rxCount - counts.lastRxCount);
240             counts.lastRxCount = counts.rxCount;
241             counts.rxCount = 0;
242 
243             long txDelta = Math.max(0, counts.txCount - counts.lastTxCount);
244             counts.lastTxCount = counts.txCount;
245             counts.txCount = 0;
246 
247             if (rxDelta != 0 || txDelta != 0) {
248                 int uid = mUidStats.keyAt(i);
249                 long[] stats = mPowerStats.uidStats.get(uid);
250                 if (stats == null) {
251                     stats = new long[mLayout.getUidStatsArrayLength()];
252                     mPowerStats.uidStats.put(uid, stats);
253                 }
254 
255                 mLayout.setUidRxBytes(stats, rxDelta);
256                 mLayout.setUidTxBytes(stats, txDelta);
257             }
258         }
259     }
260 
collectBluetoothScanStats()261     private void collectBluetoothScanStats() {
262         mBluetoothStatsRetriever.retrieveBluetoothScanTimes((uid, scanTimeMs) -> {
263             uid = mUidResolver.mapUid(uid);
264             UidStats uidStats = mUidStats.get(uid);
265             if (uidStats == null) {
266                 uidStats = new UidStats();
267                 mUidStats.put(uid, uidStats);
268             }
269             uidStats.scanTime += scanTimeMs;
270         });
271 
272         long totalScanTime = 0;
273         for (int i = mUidStats.size() - 1; i >= 0; i--) {
274             UidStats counts = mUidStats.valueAt(i);
275             if (counts.scanTime == 0) {
276                 continue;
277             }
278 
279             long delta = Math.max(0, counts.scanTime - counts.lastScanTime);
280             counts.lastScanTime = counts.scanTime;
281             counts.scanTime = 0;
282 
283             if (delta != 0) {
284                 int uid = mUidStats.keyAt(i);
285                 long[] stats = mPowerStats.uidStats.get(uid);
286                 if (stats == null) {
287                     stats = new long[mLayout.getUidStatsArrayLength()];
288                     mPowerStats.uidStats.put(uid, stats);
289                 }
290 
291                 mLayout.setUidScanTime(stats, delta);
292                 totalScanTime += delta;
293             }
294         }
295 
296         mLayout.setDeviceScanTime(mDeviceStats, totalScanTime);
297     }
298 
collectEnergyConsumers()299     private void collectEnergyConsumers() {
300         int voltageMv = mVoltageSupplier.getAsInt();
301         if (voltageMv <= 0) {
302             Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
303                     + " mV) when querying energy consumers");
304             return;
305         }
306 
307         int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
308         mLastVoltageMv = voltageMv;
309 
310         long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
311         if (energyUws == null) {
312             return;
313         }
314 
315         for (int i = energyUws.length - 1; i >= 0; i--) {
316             long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
317                     ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
318             if (energyDelta < 0) {
319                 // Likely, restart of powerstats HAL
320                 energyDelta = 0;
321             }
322             mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
323             mLastConsumedEnergyUws[i] = energyUws[i];
324         }
325     }
326 
327     @Override
onUidRemoved(int uid)328     protected void onUidRemoved(int uid) {
329         super.onUidRemoved(uid);
330         mUidStats.remove(uid);
331     }
332 }
333