diff options
| author | 2024-10-14 18:19:33 +0000 | |
|---|---|---|
| committer | 2024-11-06 21:34:43 +0000 | |
| commit | 431ae3b8da8045af90f89577f5570f3410ce0330 (patch) | |
| tree | ad1f0d9fe7982ca227afae992488155a7b010e5f | |
| parent | 8c7d7f7a701610d21d7000caf00e58c9153cecf1 (diff) | |
Rate limit the battery changed broadcast.
Bug: 362337621
Test: atest BatteryServiceTest
Flag: com.android.server.flags.rate_limit_battery_changed_broadcast
Change-Id: I9e7a3bab4282e50c443a51c9bb98059ececd18ef
3 files changed, 605 insertions, 71 deletions
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 78bc658d49c7..75cb42e7cd88 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -22,6 +22,9 @@ import static android.os.Flags.stateOfHealthPublic; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static com.android.server.health.Utils.copyV1Battery; +import static java.lang.Math.abs; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; @@ -48,6 +51,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; +import android.os.Looper; import android.os.OsProtoEnums; import android.os.PowerManager; import android.os.RemoteException; @@ -67,6 +71,7 @@ import android.util.EventLog; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.SomeArgs; @@ -84,6 +89,7 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArraySet; /** @@ -149,19 +155,112 @@ public final class BatteryService extends SystemService { private HealthInfo mHealthInfo; private final HealthInfo mLastHealthInfo = new HealthInfo(); private boolean mBatteryLevelCritical; - private int mLastBatteryStatus; - private int mLastBatteryHealth; - private boolean mLastBatteryPresent; - private int mLastBatteryLevel; - private int mLastBatteryVoltage; - private int mLastBatteryTemperature; - private boolean mLastBatteryLevelCritical; - private int mLastMaxChargingCurrent; - private int mLastMaxChargingVoltage; - private int mLastChargeCounter; - private int mLastBatteryCycleCount; - private int mLastChargingState; - private int mLastBatteryCapacityLevel; + + /** + * {@link HealthInfo#batteryStatus} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryStatus; + /** + * {@link HealthInfo#batteryHealth} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryHealth; + /** + * {@link HealthInfo#batteryPresent} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private boolean mLastBroadcastBatteryPresent; + /** + * {@link HealthInfo#batteryLevel} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryLevel; + /** + * {@link HealthInfo#batteryVoltageMillivolts} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryVoltage; + /** + * {@link HealthInfo#batteryTemperatureTenthsCelsius} value when + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryTemperature; + /** + * {@link #mBatteryLevelCritical} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: These values may be used for internal operations and/or to determine whether to trigger + * the broadcast or not. + */ + private boolean mLastBroadcastBatteryLevelCritical; + /** + * {@link HealthInfo#maxChargingCurrentMicroamps} value when + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastMaxChargingCurrent; + /** + * {@link HealthInfo#maxChargingVoltageMicrovolts} value when + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastMaxChargingVoltage; + /** + * {@link HealthInfo#batteryChargeCounterUah} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastChargeCounter; + /** + * {@link HealthInfo#batteryCycleCount} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryCycleCount; + /** + * {@link HealthInfo#chargingState} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastChargingState; + /** + * {@link HealthInfo#batteryCapacityLevel} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: This value may be used for internal operations and/or to determine whether to trigger + * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not. + */ + private int mLastBroadcastBatteryCapacityLevel; + /** + * {@link #mPlugType} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: These values may be used for internal operations and/or to determine whether to trigger + * the broadcast or not. + */ + private int mLastBroadcastPlugType = -1; // Extra state so we can detect first run + /** + * {@link #mInvalidCharger} value when {@link Intent#ACTION_BATTERY_CHANGED} + * broadcast was sent last. + * Note: These values may be used for internal operations and/or to determine whether to trigger + * the broadcast or not. + */ + private int mLastBroadcastInvalidCharger; /** * The last seen charging policy. This requires the * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be @@ -172,7 +271,6 @@ public final class BatteryService extends SystemService { private int mSequence = 1; private int mInvalidCharger; - private int mLastInvalidCharger; private int mLowBatteryWarningLevel; private int mLastLowBatteryWarningLevel; @@ -184,7 +282,6 @@ public final class BatteryService extends SystemService { private static String sSystemUiPackage; private int mPlugType; - private int mLastPlugType = -1; // Extra state so we can detect first run private boolean mBatteryLevelLow; @@ -197,6 +294,16 @@ public final class BatteryService extends SystemService { private boolean mUpdatesStopped; private boolean mBatteryInputSuspended; + /** + * Time when the voltage was updated last by HAL and we sent the + * {@link Intent#ACTION_BATTERY_CHANGED} broadcast. + * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast + * so it is possible that voltage was updated but we did not send the broadcast so in that + * case we do not update the time. + */ + @VisibleForTesting + public long mLastBroadcastVoltageUpdateTime; + private Led mLed; private boolean mSentLowBatteryBroadcast = false; @@ -211,7 +318,8 @@ public final class BatteryService extends SystemService { private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener> mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>(); - private static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic() + @VisibleForTesting + public static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic() .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); @@ -234,6 +342,25 @@ public final class BatteryService extends SystemService { private static final int MSG_BROADCAST_POWER_CONNECTION_CHANGED = 2; private static final int MSG_BROADCAST_BATTERY_LOW_OKAY = 3; + /** + * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We + * only send the broadcast and update the temperature value when the temp change is greater or + * equals to 1 degree celsius. + */ + private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10; + /** + * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We + * only send the broadcast if the last voltage was updated at least 20s seconds back and has a + * fluctuation of at least 1%. + */ + private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000; + /** + * The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We + * only send the broadcast if the last voltage was updated at least 20s seconds back and has a + * fluctuation of at least 1%. + */ + private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f; + private final Handler.Callback mLocalCallback = msg -> { switch (msg.what) { case MSG_BROADCAST_BATTERY_CHANGED: { @@ -283,10 +410,19 @@ public final class BatteryService extends SystemService { }; public BatteryService(Context context) { + this(context, Objects.requireNonNull(Looper.myLooper(), + "BatteryService uses handler!! Can't create handler inside thread that has not " + + "called Looper.prepare()")); + } + + @VisibleForTesting + public BatteryService(Context context, @NonNull Looper looper) { super(context); + Objects.requireNonNull(looper); + mContext = context; - mHandler = new Handler(mLocalCallback, true /*async*/); + mHandler = new Handler(looper, mLocalCallback, true /*async*/); mLed = new Led(context, getLocalService(LightsManager.class)); mBatteryStats = BatteryStatsService.getService(); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); @@ -436,7 +572,7 @@ public final class BatteryService extends SystemService { private boolean shouldSendBatteryLowLocked() { final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE; - final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE; + final boolean oldPlugged = mLastBroadcastPlugType != BATTERY_PLUGGED_NONE; /* The ACTION_BATTERY_LOW broadcast is sent in these situations: * - is just un-plugged (previously was plugged) and battery level is @@ -447,7 +583,7 @@ public final class BatteryService extends SystemService { return !plugged && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel - && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel + && (oldPlugged || mLastBroadcastBatteryLevel > mLowBatteryWarningLevel || mHealthInfo.batteryLevel > mLastLowBatteryWarningLevel); } @@ -515,7 +651,13 @@ public final class BatteryService extends SystemService { } } - private void update(android.hardware.health.HealthInfo info) { + /** + * Updates the healthInfo and triggers the broadcast. + * + * @param info the new health info + */ + @VisibleForTesting + public void update(android.hardware.health.HealthInfo info) { traceBegin("HealthInfoUpdate"); Trace.traceCounter( @@ -556,8 +698,8 @@ public final class BatteryService extends SystemService { long dischargeDuration = 0; mBatteryLevelCritical = - mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && mHealthInfo.batteryLevel <= mCriticalBatteryLevel; + mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN + && mHealthInfo.batteryLevel <= mCriticalBatteryLevel; mPlugType = plugType(mHealthInfo); if (DEBUG) { @@ -592,23 +734,23 @@ public final class BatteryService extends SystemService { } if (force - || (mHealthInfo.batteryStatus != mLastBatteryStatus - || mHealthInfo.batteryHealth != mLastBatteryHealth - || mHealthInfo.batteryPresent != mLastBatteryPresent - || mHealthInfo.batteryLevel != mLastBatteryLevel - || mPlugType != mLastPlugType - || mHealthInfo.batteryVoltageMillivolts != mLastBatteryVoltage - || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBatteryTemperature - || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent - || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage - || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter - || mInvalidCharger != mLastInvalidCharger - || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount - || mHealthInfo.chargingState != mLastChargingState - || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) { - - if (mPlugType != mLastPlugType) { - if (mLastPlugType == BATTERY_PLUGGED_NONE) { + || (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus + || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth + || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent + || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel + || mPlugType != mLastBroadcastPlugType + || mHealthInfo.batteryVoltageMillivolts != mLastBroadcastBatteryVoltage + || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBroadcastBatteryTemperature + || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent + || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage + || mHealthInfo.batteryChargeCounterUah != mLastBroadcastChargeCounter + || mInvalidCharger != mLastBroadcastInvalidCharger + || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount + || mHealthInfo.chargingState != mLastBroadcastChargingState + || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel)) { + + if (mPlugType != mLastBroadcastPlugType) { + if (mLastBroadcastPlugType == BATTERY_PLUGGED_NONE) { // discharging -> charging mChargeStartLevel = mHealthInfo.batteryLevel; mChargeStartTime = SystemClock.elapsedRealtime(); @@ -622,7 +764,8 @@ public final class BatteryService extends SystemService { // There's no value in this data unless we've discharged at least once and the // battery level has changed; so don't log until it does. - if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) { + if (mDischargeStartTime != 0 + && mDischargeStartLevel != mHealthInfo.batteryLevel) { dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; logOutlier = true; EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration, @@ -639,7 +782,7 @@ public final class BatteryService extends SystemService { if (mChargeStartTime != 0 && chargeDuration != 0) { final LogMaker builder = new LogMaker(MetricsEvent.ACTION_CHARGE); builder.setType(MetricsEvent.TYPE_DISMISS); - builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastPlugType); + builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastBroadcastPlugType); builder.addTaggedData(MetricsEvent.FIELD_CHARGING_DURATION_MILLIS, chargeDuration); builder.addTaggedData(MetricsEvent.FIELD_BATTERY_LEVEL_START, @@ -651,19 +794,20 @@ public final class BatteryService extends SystemService { mChargeStartTime = 0; } } - if (mHealthInfo.batteryStatus != mLastBatteryStatus || - mHealthInfo.batteryHealth != mLastBatteryHealth || - mHealthInfo.batteryPresent != mLastBatteryPresent || - mPlugType != mLastPlugType) { + if (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus + || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth + || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent + || mPlugType != mLastBroadcastPlugType) { EventLog.writeEvent(EventLogTags.BATTERY_STATUS, - mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0, + mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, + mHealthInfo.batteryPresent ? 1 : 0, mPlugType, mHealthInfo.batteryTechnology); SystemProperties.set( "debug.tracing.battery_status", Integer.toString(mHealthInfo.batteryStatus)); SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType)); } - if (mHealthInfo.batteryLevel != mLastBatteryLevel) { + if (mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel) { // Don't do this just from voltage or temperature changes, that is // too noisy. EventLog.writeEvent( @@ -672,8 +816,8 @@ public final class BatteryService extends SystemService { mHealthInfo.batteryVoltageMillivolts, mHealthInfo.batteryTemperatureTenthsCelsius); } - if (mBatteryLevelCritical && !mLastBatteryLevelCritical && - mPlugType == BATTERY_PLUGGED_NONE) { + if (mBatteryLevelCritical && !mLastBroadcastBatteryLevelCritical + && mPlugType == BATTERY_PLUGGED_NONE) { // We want to make sure we log discharge cycle outliers // if the battery is about to die. dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; @@ -684,7 +828,7 @@ public final class BatteryService extends SystemService { // Should we now switch in to low battery mode? if (mPlugType == BATTERY_PLUGGED_NONE && mHealthInfo.batteryStatus != - BatteryManager.BATTERY_STATUS_UNKNOWN + BatteryManager.BATTERY_STATUS_UNKNOWN && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) { mBatteryLevelLow = true; } @@ -692,7 +836,7 @@ public final class BatteryService extends SystemService { // Should we now switch out of low battery mode? if (mPlugType != BATTERY_PLUGGED_NONE) { mBatteryLevelLow = false; - } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) { + } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) { mBatteryLevelLow = false; } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) { // If being forced, the previous state doesn't matter, we will just @@ -706,7 +850,7 @@ public final class BatteryService extends SystemService { // Separate broadcast is sent for power connected / not connected // since the standard intent will not wake any applications and some // applications may want to have smart behavior based on this. - if (mPlugType != 0 && mLastPlugType == 0) { + if (mPlugType != 0 && mLastBroadcastPlugType == 0) { final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED); statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); @@ -726,8 +870,7 @@ public final class BatteryService extends SystemService { } }); } - } - else if (mPlugType == 0 && mLastPlugType != 0) { + } else if (mPlugType == 0 && mLastBroadcastPlugType != 0) { final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED); statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence); @@ -797,8 +940,14 @@ public final class BatteryService extends SystemService { // We are doing this after sending the above broadcasts, so anything processing // them will get the new sequence number at that point. (See for example how testing // of JobScheduler's BatteryController works.) - sendBatteryChangedIntentLocked(force); - if (mLastBatteryLevel != mHealthInfo.batteryLevel || mLastPlugType != mPlugType) { + + boolean rateLimitBatteryChangedBroadcast = rateLimitBatteryChangedBroadcast(force); + + if (!rateLimitBatteryChangedBroadcast) { + sendBatteryChangedIntentLocked(force); + } + if (mLastBroadcastBatteryLevel != mHealthInfo.batteryLevel + || mLastBroadcastPlugType != mPlugType) { sendBatteryLevelChangedIntentLocked(); } @@ -811,21 +960,24 @@ public final class BatteryService extends SystemService { logOutlierLocked(dischargeDuration); } - mLastBatteryStatus = mHealthInfo.batteryStatus; - mLastBatteryHealth = mHealthInfo.batteryHealth; - mLastBatteryPresent = mHealthInfo.batteryPresent; - mLastBatteryLevel = mHealthInfo.batteryLevel; - mLastPlugType = mPlugType; - mLastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts; - mLastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius; - mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps; - mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts; - mLastChargeCounter = mHealthInfo.batteryChargeCounterUah; - mLastBatteryLevelCritical = mBatteryLevelCritical; - mLastInvalidCharger = mInvalidCharger; - mLastBatteryCycleCount = mHealthInfo.batteryCycleCount; - mLastChargingState = mHealthInfo.chargingState; - mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel; + // Only update the values when we send the broadcast + if (!rateLimitBatteryChangedBroadcast) { + mLastBroadcastBatteryStatus = mHealthInfo.batteryStatus; + mLastBroadcastBatteryHealth = mHealthInfo.batteryHealth; + mLastBroadcastBatteryPresent = mHealthInfo.batteryPresent; + mLastBroadcastBatteryLevel = mHealthInfo.batteryLevel; + mLastBroadcastPlugType = mPlugType; + mLastBroadcastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts; + mLastBroadcastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius; + mLastBroadcastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps; + mLastBroadcastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts; + mLastBroadcastChargeCounter = mHealthInfo.batteryChargeCounterUah; + mLastBroadcastBatteryLevelCritical = mBatteryLevelCritical; + mLastBroadcastInvalidCharger = mInvalidCharger; + mLastBroadcastBatteryCycleCount = mHealthInfo.batteryCycleCount; + mLastBroadcastChargingState = mHealthInfo.chargingState; + mLastBroadcastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel; + } } } @@ -1089,6 +1241,75 @@ public final class BatteryService extends SystemService { } } + /** + * Rate limit's the broadcast based on the changes in temp, voltage and chargeCounter. + */ + private boolean rateLimitBatteryChangedBroadcast(boolean forceUpdate) { + if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) { + return false; + } + if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + return false; + } + + final boolean voltageUpdated = + mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts; + final boolean temperatureUpdated = + mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius; + final boolean otherStatesUpdated = forceUpdate + || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus + || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth + || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent + || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel + || mPlugType != mLastBroadcastPlugType + || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent + || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage + || mInvalidCharger != mLastBroadcastInvalidCharger + || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount + || mHealthInfo.chargingState != mLastBroadcastChargingState + || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel + || mLastBroadcastChargeCounter != mHealthInfo.batteryChargeCounterUah; + + // We only rate limit based on changes in the temp, voltage. + if (otherStatesUpdated) { + + if (voltageUpdated) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + } + return false; + } + + final float basePointDiff = + (float) (mLastBroadcastBatteryVoltage - mHealthInfo.batteryVoltageMillivolts) + / mLastBroadcastBatteryVoltage; + + // We only send the broadcast if voltage change is greater than 1% and last voltage + // update was sent at least 20 seconds back. + if (voltageUpdated + && abs(basePointDiff) >= BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE + && SystemClock.elapsedRealtime() - mLastBroadcastVoltageUpdateTime + >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + + return false; + } + + // Only send the broadcast if the temperature update is greater than 1 degree celsius. + if (temperatureUpdated + && abs( + mLastBroadcastBatteryTemperature - mHealthInfo.batteryTemperatureTenthsCelsius) + >= ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE) { + + if (voltageUpdated) { + mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime(); + } + return false; + } + + return true; + } + class Shell extends ShellCommand { @Override public int onCommand(String cmd) { @@ -1399,6 +1620,10 @@ public final class BatteryService extends SystemService { pw.println(" level: " + mHealthInfo.batteryLevel); pw.println(" scale: " + BATTERY_SCALE); pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts); + pw.println(" Time when the latest updated value of the voltage was sent via " + + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime); + pw.println(" The last voltage value sent via the battery changed broadcast: " + + mLastBroadcastBatteryVoltage); pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius); pw.println(" technology: " + mHealthInfo.batteryTechnology); pw.println(" Charging state: " + mHealthInfo.chargingState); @@ -1457,6 +1682,11 @@ public final class BatteryService extends SystemService { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } + @VisibleForTesting + public Handler getHandlerForTest() { + return mHandler; + } + @SuppressLint("AndroidFrameworkRequiresPermission") private static void sendBroadcastToAllUsers(Context context, Intent intent, Bundle options) { diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig index 69ba785b3b4f..eea5c982c537 100644 --- a/services/core/java/com/android/server/flags/services.aconfig +++ b/services/core/java/com/android/server/flags/services.aconfig @@ -67,3 +67,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "rate_limit_battery_changed_broadcast" + description: "Optimize the delivery of the battery changed broadcast by rate limiting the frequency of the updates" + bug: "362337621" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java new file mode 100644 index 000000000000..df11a3dd5db8 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 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; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.content.Context; +import android.hardware.health.HealthInfo; +import android.os.HandlerThread; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.R; +import com.android.internal.app.IBatteryStats; +import com.android.modules.utils.testing.ExtendedMockitoRule; +import com.android.server.am.BatteryStatsService; +import com.android.server.flags.Flags; +import com.android.server.lights.LightsManager; +import com.android.server.lights.LogicalLight; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class BatteryServiceTest { + + private static final int CURRENT_BATTERY_VOLTAGE = 3000; + private static final int VOLTAGE_LESS_THEN_ONE_PERCENT = 3029; + private static final int VOLTAGE_MORE_THEN_ONE_PERCENT = 3030; + private static final int CURRENT_BATTERY_TEMP = 300; + private static final int TEMP_LESS_THEN_ONE_DEGREE_CELSIUS = 305; + private static final int TEMP_MORE_THEN_ONE_DEGREE_CELSIUS = 310; + private static final int CURRENT_BATTERY_HEALTH = 2; + private static final int UPDATED_BATTERY_HEALTH = 3; + private static final int HANDLER_IDLE_TIME_MS = 1000; + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) + .mockStatic(SystemProperties.class) + .mockStatic(ActivityManager.class) + .mockStatic(BatteryStatsService.class) + .build(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock + private Context mContextMock; + @Mock + private LightsManager mLightsManagerMock; + @Mock + private ActivityManagerInternal mActivityManagerInternalMock; + @Mock + private IBatteryStats mIBatteryStatsMock; + + private BatteryService mBatteryService; + private String mSystemUiPackage; + + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mSystemUiPackage = InstrumentationRegistry.getInstrumentation().getTargetContext() + .getResources().getString(R.string.config_systemUi); + + when(mLightsManagerMock.getLight(anyInt())).thenReturn(mock(LogicalLight.class)); + when(mActivityManagerInternalMock.isSystemReady()).thenReturn(true); + when(mContextMock.getResources()).thenReturn( + InstrumentationRegistry.getInstrumentation().getTargetContext().getResources()); + ExtendedMockito.when(BatteryStatsService.getService()).thenReturn(mIBatteryStatsMock); + + doNothing().when(mIBatteryStatsMock).setBatteryState(anyInt(), anyInt(), anyInt(), anyInt(), + anyInt(), anyInt(), anyInt(), anyInt(), anyLong()); + doNothing().when(() -> SystemProperties.set(anyString(), anyString())); + doNothing().when(() -> ActivityManager.broadcastStickyIntent(any(), + eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE), + eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL))); + + addLocalServiceMock(LightsManager.class, mLightsManagerMock); + addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock); + + createBatteryService(); + } + + @Test + public void createBatteryService_withNullLooper_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new BatteryService(mContextMock)); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() { + mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyVoltageUpdated_beforeTwentySeconds_broadcastNotSent() { + mBatteryService.update( + createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyVoltageUpdated_broadcastSent() { + mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000; + mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() { + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(0); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void tempUpdated_broadcastSent() { + long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime; + mBatteryService.update( + createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime); + verifyNumberOfTimesBroadcastSent(1); + } + + @Test + @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() { + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP, + UPDATED_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + + // updating the voltage just after the health update does not triggers the broadcast. + mBatteryService.update( + createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + UPDATED_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + } + + @Test + @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST) + public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() { + mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + + verifyNumberOfTimesBroadcastSent(1); + } + + private HealthInfo createHealthInfo( + int batteryVoltage, + int batteryTemperature, + int batteryHealth) { + HealthInfo h = new HealthInfo(); + h.batteryVoltageMillivolts = batteryVoltage; + h.batteryTemperatureTenthsCelsius = batteryTemperature; + h.batteryChargeCounterUah = 4680000; + h.batteryStatus = 5; + h.batteryHealth = batteryHealth; + h.batteryPresent = true; + h.batteryLevel = 100; + h.maxChargingCurrentMicroamps = 298125; + h.batteryCurrentAverageMicroamps = -2812; + h.batteryCurrentMicroamps = 298125; + h.maxChargingVoltageMicrovolts = 3000; + h.batteryCycleCount = 50; + h.chargingState = 4; + h.batteryCapacityLevel = 100; + return h; + } + + // Creates a new battery service objects and sets the initial values. + private void createBatteryService() { + final HandlerThread handlerThread = new HandlerThread("BatteryServiceTest"); + handlerThread.start(); + + mBatteryService = new BatteryService(mContextMock, handlerThread.getLooper()); + + // trigger the update to set the initial values. + mBatteryService.update( + createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP, + CURRENT_BATTERY_HEALTH)); + + waitForHandlerToExecute(); + } + + private void waitForHandlerToExecute() { + final CountDownLatch latch = new CountDownLatch(1); + mBatteryService.getHandlerForTest().post(latch::countDown); + boolean isExecutionComplete = false; + + try { + isExecutionComplete = latch.await(HANDLER_IDLE_TIME_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("Handler interrupted before executing the message " + e); + } + + assertTrue("Timed out while waiting for Handler to execute.", isExecutionComplete); + } + + private void verifyNumberOfTimesBroadcastSent(int numberOfTimes) { + // Increase the numberOfTimes by 1 as one broadcast was sent initially during the test + // setUp. + verify(() -> ActivityManager.broadcastStickyIntent(any(), + eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE), + eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)), + times(++numberOfTimes)); + } + +} |