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