diff options
| -rw-r--r-- | services/core/java/com/android/server/stats/pull/BatteryHealthUtility.java | 89 | ||||
| -rw-r--r-- | services/core/java/com/android/server/stats/pull/StatsPullAtomService.java | 53 |
2 files changed, 141 insertions, 1 deletions
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 000000000000..e0768fe1f0e5 --- /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 c1b825b3f8d1..0041d39f4b2b 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.NetworkStats; 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.File; 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { registerFullBatteryCapacity(); registerBatteryVoltage(); registerBatteryCycleCount(); + registerBatteryHealth(); registerSettingsStats(); registerInstalledIncrementalPackages(); registerKeystoreStorageStats(); @@ -4365,7 +4370,15 @@ public class StatsPullAtomService extends SystemService { ); } - 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 @@ public class StatsPullAtomService extends SystemService { 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; } |