Merge "Create puller for batteryHealthData" into main
diff --git a/services/core/java/com/android/server/stats/pull/BatteryHealthUtility.java b/services/core/java/com/android/server/stats/pull/BatteryHealthUtility.java
new file mode 100644
index 0000000..e0768fe
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/BatteryHealthUtility.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 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 com.android.server.stats.pull;
+
+import android.util.StatsEvent;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * Utility class to redact Battery Health data from HealthServiceWrapper
+ *
+ * @hide
+ */
+public abstract class BatteryHealthUtility {
+ /**
+ * Create a StatsEvent corresponding to the Battery Health data, the fields
+ * of which are redacted to preserve users' privacy.
+ * The redaction consists in truncating the timestamps to the Monday of the
+ * corresponding week, and reducing the battery serial into the last byte
+ * of its MD5.
+ */
+ public static StatsEvent buildStatsEvent(int atomTag,
+ android.hardware.health.BatteryHealthData data, int chargeStatus, int chargePolicy)
+ throws NoSuchAlgorithmException {
+ int manufacturingDate = secondsToWeekYYYYMMDD(data.batteryManufacturingDateSeconds);
+ int firstUsageDate = secondsToWeekYYYYMMDD(data.batteryFirstUsageSeconds);
+ long stateOfHealth = data.batteryStateOfHealth;
+ int partStatus = data.batteryPartStatus;
+ int serialHashTruncated = stringToIntHash(data.batterySerialNumber) & 0xFF; // Last byte
+
+ return FrameworkStatsLog.buildStatsEvent(atomTag, manufacturingDate, firstUsageDate,
+ (int) stateOfHealth, serialHashTruncated, partStatus, chargeStatus, chargePolicy);
+ }
+
+ private static int secondsToWeekYYYYMMDD(long seconds) {
+ Calendar calendar = Calendar.getInstance();
+ long millis = seconds * 1000L;
+
+ calendar.setTimeInMillis(millis);
+
+ // Truncate all date information, up to week, which is rounded to
+ // MONDAY
+ calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.US);
+
+ String formattedDate = sdf.format(calendar.getTime());
+
+ return Integer.parseInt(formattedDate);
+ }
+
+ private static int stringToIntHash(String data) throws NoSuchAlgorithmException {
+ if (data == null || data.isEmpty()) {
+ return 0;
+ }
+
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+ byte[] hashBytes = digest.digest(data.getBytes());
+
+ // Convert to integer (simplest way, but potential for loss of information)
+ BigInteger bigInt = new BigInteger(1, hashBytes);
+ return bigInt.intValue();
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index c1b825b..0041d39 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -119,6 +119,8 @@
import android.net.NetworkTemplate;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.BatteryProperty;
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.BatteryStatsManager;
@@ -243,6 +245,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@@ -769,6 +772,7 @@
case FrameworkStatsLog.FULL_BATTERY_CAPACITY:
case FrameworkStatsLog.BATTERY_VOLTAGE:
case FrameworkStatsLog.BATTERY_CYCLE_COUNT:
+ case FrameworkStatsLog.BATTERY_HEALTH:
synchronized (mHealthHalLock) {
return pullHealthHalLocked(atomTag, data);
}
@@ -999,6 +1003,7 @@
registerFullBatteryCapacity();
registerBatteryVoltage();
registerBatteryCycleCount();
+ registerBatteryHealth();
registerSettingsStats();
registerInstalledIncrementalPackages();
registerKeystoreStorageStats();
@@ -4365,7 +4370,15 @@
);
}
- int pullHealthHalLocked(int atomTag, List<StatsEvent> pulledData) {
+ private void registerBatteryHealth() {
+ int tagId = FrameworkStatsLog.BATTERY_HEALTH;
+ mStatsManager.setPullAtomCallback(tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR, mStatsCallbackImpl);
+ }
+
+ @GuardedBy("mHealthHalLock")
+ private int pullHealthHalLocked(int atomTag, List<StatsEvent> pulledData) {
if (mHealthService == null) {
return StatsManager.PULL_SKIP;
}
@@ -4396,6 +4409,44 @@
case FrameworkStatsLog.BATTERY_CYCLE_COUNT:
pulledValue = healthInfo.batteryCycleCount;
break;
+ case FrameworkStatsLog.BATTERY_HEALTH:
+ android.hardware.health.BatteryHealthData bhd;
+ try {
+ bhd = mHealthService.getBatteryHealthData();
+ } catch (RemoteException | IllegalStateException e) {
+ return StatsManager.PULL_SKIP;
+ }
+ if (bhd == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ StatsEvent batteryHealthEvent;
+ try {
+ BatteryProperty chargeStatusProperty = new BatteryProperty();
+ BatteryProperty chargePolicyProperty = new BatteryProperty();
+
+ if (0 > mHealthService.getProperty(
+ BatteryManager.BATTERY_PROPERTY_STATUS, chargeStatusProperty)) {
+ return StatsManager.PULL_SKIP;
+ }
+ if (0 > mHealthService.getProperty(
+ BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY,
+ chargePolicyProperty)) {
+ return StatsManager.PULL_SKIP;
+ }
+ int chargeStatus = (int) chargeStatusProperty.getLong();
+ int chargePolicy = (int) chargePolicyProperty.getLong();
+ batteryHealthEvent = BatteryHealthUtility.buildStatsEvent(
+ atomTag, bhd, chargeStatus, chargePolicy);
+ pulledData.add(batteryHealthEvent);
+
+ return StatsManager.PULL_SUCCESS;
+ } catch (RemoteException | IllegalStateException e) {
+ Slog.e(TAG, "Failed to add pulled data", e);
+ } catch (NoSuchAlgorithmException e) {
+ Slog.e(TAG, "Could not find message digest algorithm", e);
+ }
+ return StatsManager.PULL_SKIP;
default:
return StatsManager.PULL_SKIP;
}