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