Add BatteryStatsManager.getBluetoothBatteryStats() API
Bug: 202876405
Test: atest FrameworksCoreTests:BatteryStatsImplTest
Change-Id: I4a7f880ddad7dc98e9919356859603b17a894705
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 4666c5c..2d33817 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3406,6 +3406,11 @@
public abstract WakeLockStats getWakeLockStats();
/**
+ * Returns aggregated Bluetooth stats.
+ */
+ public abstract BluetoothBatteryStats getBluetoothBatteryStats();
+
+ /**
* Returns Timers tracking the total time of each Resource Power Manager state and voter.
*/
public abstract Map<String, ? extends Timer> getRpmStats();
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 6339435..2a609b8 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -368,6 +368,21 @@
}
/**
+ * Retrieves accumulated bluetooth stats.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+ @NonNull
+ public BluetoothBatteryStats getBluetoothBatteryStats() {
+ try {
+ return mBatteryStats.getBluetoothBatteryStats();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Indicates an app acquiring full wifi lock.
*
* @param ws worksource (to be used for battery blaming).
diff --git a/core/java/android/os/BluetoothBatteryStats.aidl b/core/java/android/os/BluetoothBatteryStats.aidl
new file mode 100644
index 0000000..d0514b6
--- /dev/null
+++ b/core/java/android/os/BluetoothBatteryStats.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** {@hide} */
+parcelable BluetoothBatteryStats;
diff --git a/core/java/android/os/BluetoothBatteryStats.java b/core/java/android/os/BluetoothBatteryStats.java
new file mode 100644
index 0000000..3d99a08
--- /dev/null
+++ b/core/java/android/os/BluetoothBatteryStats.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Snapshot of Bluetooth battery stats.
+ *
+ * @hide
+ */
+public class BluetoothBatteryStats implements Parcelable {
+
+ /** @hide */
+ public static class UidStats {
+ public final int uid;
+ public final long scanTimeMs;
+ public final long unoptimizedScanTimeMs;
+ public final int scanResultCount;
+ public final long rxTimeMs;
+ public final long txTimeMs;
+
+ public UidStats(int uid, long scanTimeMs, long unoptimizedScanTimeMs, int scanResultCount,
+ long rxTimeMs, long txTimeMs) {
+ this.uid = uid;
+ this.scanTimeMs = scanTimeMs;
+ this.unoptimizedScanTimeMs = unoptimizedScanTimeMs;
+ this.scanResultCount = scanResultCount;
+ this.rxTimeMs = rxTimeMs;
+ this.txTimeMs = txTimeMs;
+ }
+
+ private UidStats(Parcel in) {
+ uid = in.readInt();
+ scanTimeMs = in.readLong();
+ unoptimizedScanTimeMs = in.readLong();
+ scanResultCount = in.readInt();
+ rxTimeMs = in.readLong();
+ txTimeMs = in.readLong();
+ }
+
+ private void writeToParcel(Parcel out) {
+ out.writeInt(uid);
+ out.writeLong(scanTimeMs);
+ out.writeLong(unoptimizedScanTimeMs);
+ out.writeInt(scanResultCount);
+ out.writeLong(rxTimeMs);
+ out.writeLong(txTimeMs);
+ }
+
+ @Override
+ public String toString() {
+ return "UidStats{"
+ + "uid=" + uid
+ + ", scanTimeMs=" + scanTimeMs
+ + ", unoptimizedScanTimeMs=" + unoptimizedScanTimeMs
+ + ", scanResultCount=" + scanResultCount
+ + ", rxTimeMs=" + rxTimeMs
+ + ", txTimeMs=" + txTimeMs
+ + '}';
+ }
+ }
+
+ private final List<UidStats> mUidStats;
+
+ public BluetoothBatteryStats(@NonNull List<UidStats> uidStats) {
+ mUidStats = uidStats;
+ }
+
+ @NonNull
+ public List<UidStats> getUidStats() {
+ return mUidStats;
+ }
+
+ protected BluetoothBatteryStats(Parcel in) {
+ final int size = in.readInt();
+ mUidStats = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mUidStats.add(new UidStats(in));
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ final int size = mUidStats.size();
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ UidStats stats = mUidStats.get(i);
+ stats.writeToParcel(out);
+ }
+ }
+
+ public static final Creator<BluetoothBatteryStats> CREATOR =
+ new Creator<BluetoothBatteryStats>() {
+ @Override
+ public BluetoothBatteryStats createFromParcel(Parcel in) {
+ return new BluetoothBatteryStats(in);
+ }
+
+ @Override
+ public BluetoothBatteryStats[] newArray(int size) {
+ return new BluetoothBatteryStats[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 587876d..9648008 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -21,6 +21,7 @@
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
+import android.os.BluetoothBatteryStats;
import android.os.ParcelFileDescriptor;
import android.os.WakeLockStats;
import android.os.WorkSource;
@@ -162,6 +163,10 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)")
WakeLockStats getWakeLockStats();
+ /** {@hide} */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)")
+ BluetoothBatteryStats getBluetoothBatteryStats();
+
HealthStatsParceler takeUidSnapshot(int uid);
HealthStatsParceler[] takeUidSnapshots(in int[] uid);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 7c203fb..400cbd2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -45,6 +45,7 @@
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Binder;
+import android.os.BluetoothBatteryStats;
import android.os.Build;
import android.os.Handler;
import android.os.IBatteryPropertiesRegistrar;
@@ -1196,6 +1197,48 @@
return new WakeLockStats(uidWakeLockStats);
}
+ @Override
+ @GuardedBy("this")
+ public BluetoothBatteryStats getBluetoothBatteryStats() {
+ final long elapsedRealtimeUs = mClock.elapsedRealtime() * 1000;
+ ArrayList<BluetoothBatteryStats.UidStats> uidStats = new ArrayList<>();
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ final Uid uid = mUidStats.valueAt(i);
+ final Timer scanTimer = uid.getBluetoothScanTimer();
+ final long scanTimeMs =
+ scanTimer != null ? scanTimer.getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0;
+
+ final Timer unoptimizedScanTimer = uid.getBluetoothUnoptimizedScanTimer();
+ final long unoptimizedScanTimeMs =
+ unoptimizedScanTimer != null ? unoptimizedScanTimer.getTotalTimeLocked(
+ elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0;
+
+ final Counter scanResultCounter = uid.getBluetoothScanResultCounter();
+ final int scanResultCount =
+ scanResultCounter != null ? scanResultCounter.getCountLocked(
+ STATS_SINCE_CHARGED) : 0;
+
+ final ControllerActivityCounter counter = uid.getBluetoothControllerActivity();
+ final long rxTimeMs = counter != null ? counter.getRxTimeCounter().getCountLocked(
+ STATS_SINCE_CHARGED) : 0;
+ final long txTimeMs = counter != null ? counter.getTxTimeCounters()[0].getCountLocked(
+ STATS_SINCE_CHARGED) : 0;
+
+ if (scanTimeMs != 0 || unoptimizedScanTimeMs != 0 || scanResultCount != 0
+ || rxTimeMs != 0 || txTimeMs != 0) {
+ uidStats.add(new BluetoothBatteryStats.UidStats(uid.getUid(),
+ scanTimeMs,
+ unoptimizedScanTimeMs,
+ scanResultCount,
+ rxTimeMs,
+ txTimeMs));
+ }
+ }
+
+ return new BluetoothBatteryStats(uidStats);
+ }
+
String mLastWakeupReason = null;
long mLastWakeupUptimeMs = 0;
private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>();
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 388cf6e..be8045d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -37,8 +37,12 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.UidTraffic;
import android.os.BatteryStats;
+import android.os.BluetoothBatteryStats;
import android.os.WakeLockStats;
+import android.os.WorkSource;
import android.util.SparseArray;
import android.view.Display;
@@ -47,6 +51,8 @@
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +72,8 @@
private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
@Mock
private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+ @Mock
+ private PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
private MockBatteryStatsImpl mBatteryStatsImpl;
@@ -79,6 +87,7 @@
when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
+ .setPowerProfile(mPowerProfile)
.setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
.setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
}
@@ -559,4 +568,38 @@
assertThat(wakeLock2.timeHeldMs).isEqualTo(3000); // 9000-6000
assertThat(wakeLock2.totalTimeHeldMs).isEqualTo(4000); // (5000-4000) + (9000-6000)
}
+
+ @Test
+ public void testGetBluetoothBatteryStats() {
+ when(mPowerProfile.getAveragePower(
+ PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0);
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+
+ final WorkSource ws = new WorkSource(10042);
+ mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, false, 1000, 1000);
+ mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, false, 5000, 5000);
+ mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, true, 6000, 6000);
+ mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, true, 9000, 9000);
+ mBatteryStatsImpl.noteBluetoothScanResultsFromSourceLocked(ws, 42, 9000, 9000);
+
+ BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
+ BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 9000, 8000, 12000, 0);
+ info.setUidTraffic(ImmutableList.of(
+ new UidTraffic(10042, 3000, 4000),
+ new UidTraffic(10043, 5000, 8000)));
+ mBatteryStatsImpl.updateBluetoothStateLocked(info, -1, 1000, 1000);
+
+ BluetoothBatteryStats stats =
+ mBatteryStatsImpl.getBluetoothBatteryStats();
+ assertThat(stats.getUidStats()).hasSize(2);
+
+ final BluetoothBatteryStats.UidStats uidStats =
+ stats.getUidStats().stream().filter(u -> u.uid == 10042).findFirst().get();
+ assertThat(uidStats.scanTimeMs).isEqualTo(7000); // 4000+3000
+ assertThat(uidStats.unoptimizedScanTimeMs).isEqualTo(3000);
+ assertThat(uidStats.scanResultCount).isEqualTo(42);
+ assertThat(uidStats.rxTimeMs).isEqualTo(7375); // Some scan time is treated as RX
+ assertThat(uidStats.txTimeMs).isEqualTo(7666); // Some scan time is treated as TX
+ }
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 921208c..0b92954 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -42,6 +42,7 @@
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Binder;
+import android.os.BluetoothBatteryStats;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -2627,6 +2628,20 @@
}
/**
+ * Gets a snapshot of Bluetooth stats
+ * @hide
+ */
+ public BluetoothBatteryStats getBluetoothBatteryStats() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BATTERY_STATS, null);
+
+ // Wait for the completion of pending works if there is any
+ awaitCompletion();
+ synchronized (mStats) {
+ return mStats.getBluetoothBatteryStats();
+ }
+ }
+
+ /**
* Gets a snapshot of the system health for a particular uid.
*/
@Override