Merge changes from topic "rate limit battery changed broadcast" into main
* changes:
Don't trigger batterychanged broadcast when ChargeCounter is updated.
Rate limit the battery changed broadcast.
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 78bc658..3dcca14 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 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.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.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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;
/**
@@ -149,19 +155,112 @@
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 @@
private int mSequence = 1;
private int mInvalidCharger;
- private int mLastInvalidCharger;
private int mLowBatteryWarningLevel;
private int mLastLowBatteryWarningLevel;
@@ -184,7 +282,6 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
return !plugged
&& mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
- && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel
+ && (oldPlugged || mLastBroadcastBatteryLevel > mLowBatteryWarningLevel
|| mHealthInfo.batteryLevel > mLastLowBatteryWarningLevel);
}
@@ -515,7 +651,13 @@
}
}
- 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 @@
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) {
@@ -591,24 +733,28 @@
mHandler.post(this::notifyChargingPolicyChanged);
}
- 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)) {
+ final boolean includeChargeCounter =
+ !com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()
+ && mHealthInfo.batteryChargeCounterUah != mLastBroadcastChargeCounter;
- if (mPlugType != mLastPlugType) {
- if (mLastPlugType == BATTERY_PLUGGED_NONE) {
+ if (force
+ || (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
+ || includeChargeCounter
+ || 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 +768,8 @@
// 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 +786,7 @@
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 +798,20 @@
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 +820,8 @@
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 +832,7 @@
// 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 +840,7 @@
// 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 +854,7 @@
// 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 +874,7 @@
}
});
}
- }
- 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 +944,14 @@
// 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 +964,24 @@
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 +1245,74 @@
}
}
+ /**
+ * 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;
+
+ // 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 +1623,10 @@
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 +1685,11 @@
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 69ba785..eea5c98 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 @@
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 0000000..5e2f80b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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 CURRENT_CHARGE_COUNTER = 4680000;
+ private static final int UPDATED_CHARGE_COUNTER = 4218000;
+ private static final int HANDLER_IDLE_TIME_MS = 5000;
+ @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_CHARGE_COUNTER, 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_CHARGE_COUNTER,
+ 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_CHARGE_COUNTER, 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_CHARGE_COUNTER, 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,
+ UPDATED_CHARGE_COUNTER, 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,
+ CURRENT_CHARGE_COUNTER,
+ UPDATED_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+
+ // updating counter just after the health update does not triggers broadcast.
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ 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_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyChargeCounterUpdated_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void chargeCounterUpdated_tempUpdatedLessThanOneDegree_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyChargeCounterUpdated_broadcastSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ UPDATED_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ private HealthInfo createHealthInfo(
+ int batteryVoltage,
+ int batteryTemperature,
+ int batteryChargeCounter,
+ int batteryHealth) {
+ HealthInfo h = new HealthInfo();
+ h.batteryVoltageMillivolts = batteryVoltage;
+ h.batteryTemperatureTenthsCelsius = batteryTemperature;
+ h.batteryChargeCounterUah = batteryChargeCounter;
+ 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() throws InterruptedException {
+ 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_CHARGE_COUNTER,
+ 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));
+ }
+}