Merge changes from topic "cherrypick-config_batteryStatsResetOnUnplugHighBatteryLevel-zmiab0332q" into tm-qpr-dev

* changes:
  Reset BatteryStats when device has been plugged in for a long time.
  Add config to control BatteryStats reset logic
This commit is contained in:
Michael Wachenschwanz
2023-02-22 17:35:03 +00:00
committed by Android (Google) Code Review
7 changed files with 661 additions and 27 deletions

View File

@@ -81,6 +81,7 @@ import android.telephony.ServiceState.RegState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -201,6 +202,7 @@ public class BatteryStatsImpl extends BatteryStats {
public static final int RESET_REASON_ADB_COMMAND = 2;
public static final int RESET_REASON_FULL_CHARGE = 3;
public static final int RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE = 4;
public static final int RESET_REASON_PLUGGED_IN_FOR_LONG_DURATION = 5;
protected Clock mClock;
@@ -387,6 +389,89 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
/** Provide BatteryStatsImpl configuration choices */
public static class BatteryStatsConfig {
static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0;
static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1;
private final int mFlags;
private BatteryStatsConfig(Builder builder) {
int flags = 0;
if (builder.mResetOnUnplugHighBatteryLevel) {
flags |= RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG;
}
if (builder.mResetOnUnplugAfterSignificantCharge) {
flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
mFlags = flags;
}
/**
* Returns whether a BatteryStats reset should occur on unplug when the battery level is
* high.
*/
boolean shouldResetOnUnplugHighBatteryLevel() {
return (mFlags & RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG)
== RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG;
}
/**
* Returns whether a BatteryStats reset should occur on unplug if the battery charge a
* significant amount since it has been plugged in.
*/
boolean shouldResetOnUnplugAfterSignificantCharge() {
return (mFlags & RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG)
== RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
/**
* Builder for BatteryStatsConfig
*/
public static class Builder {
private boolean mResetOnUnplugHighBatteryLevel;
private boolean mResetOnUnplugAfterSignificantCharge;
public Builder() {
mResetOnUnplugHighBatteryLevel = true;
mResetOnUnplugAfterSignificantCharge = true;
}
/**
* Build the BatteryStatsConfig.
*/
public BatteryStatsConfig build() {
return new BatteryStatsConfig(this);
}
/**
* Set whether a BatteryStats reset should occur on unplug when the battery level is
* high.
*/
public Builder setResetOnUnplugHighBatteryLevel(boolean reset) {
mResetOnUnplugHighBatteryLevel = reset;
return this;
}
/**
* Set whether a BatteryStats reset should occur on unplug if the battery charge a
* significant amount since it has been plugged in.
*/
public Builder setResetOnUnplugAfterSignificantCharge(boolean reset) {
mResetOnUnplugAfterSignificantCharge = reset;
return this;
}
}
}
/** Handles calls to AlarmManager */
public interface AlarmInterface {
/** Schedule an RTC alarm */
void schedule(long rtcTimeMs, long windowLengthMs);
/** Cancel the previously scheduled alarm */
void cancel();
}
private final PlatformIdleStateCallback mPlatformIdleStateCallback;
private final Runnable mDeferSetCharging = new Runnable() {
@@ -717,6 +802,7 @@ public class BatteryStatsImpl extends BatteryStats {
protected boolean mHaveBatteryLevel = false;
protected boolean mRecordingHistory = false;
int mNumHistoryItems;
private long mBatteryPluggedInRealTimeMs = 0;
private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
@@ -1459,6 +1545,13 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
protected final Constants mConstants;
@VisibleForTesting
@GuardedBy("this")
protected BatteryStatsConfig mBatteryStatsConfig = new BatteryStatsConfig.Builder().build();
@VisibleForTesting
protected AlarmInterface mLongPlugInAlarmInterface = null;
/*
* Holds a SamplingTimer associated with each Resource Power Manager state and voter,
* recording their times when on-battery (regardless of screen state).
@@ -1625,12 +1718,13 @@ public class BatteryStatsImpl extends BatteryStats {
public BatteryStatsImpl(Clock clock, File historyDirectory) {
init(clock);
mStartClockTimeMs = clock.currentTimeMillis();
mCheckinFile = null;
mDailyFile = null;
if (historyDirectory == null) {
mCheckinFile = null;
mStatsFile = null;
mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
} else {
mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
mBatteryStatsHistory = new BatteryStatsHistory(this, historyDirectory, mHistoryBuffer);
}
@@ -12547,6 +12641,27 @@ public class BatteryStatsImpl extends BatteryStats {
return mPowerProfile;
}
/**
* Injects BatteryStatsConfig
*/
public void setBatteryStatsConfig(BatteryStatsConfig config) {
synchronized (this) {
mBatteryStatsConfig = config;
}
}
/**
* Injects a LongPlugInAlarmHandler
*/
public void setLongPlugInAlarmInterface(AlarmInterface longPlugInAlarmInterface) {
synchronized (this) {
mLongPlugInAlarmInterface = longPlugInAlarmInterface;
if (!mOnBattery) {
scheduleNextResetWhilePluggedInCheck();
}
}
}
/**
* Starts tracking CPU time-in-state for threads of the system server process,
* keeping a separate account of threads receiving incoming binder calls.
@@ -13019,12 +13134,12 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
public void resetAllStatsCmdLocked() {
public void resetAllStatsAndHistoryLocked(int reason) {
final long mSecUptime = mClock.uptimeMillis();
long uptimeUs = mSecUptime * 1000;
long mSecRealtime = mClock.elapsedRealtime();
long realtimeUs = mSecRealtime * 1000;
resetAllStatsLocked(mSecUptime, mSecRealtime, RESET_REASON_ADB_COMMAND);
resetAllStatsLocked(mSecUptime, mSecRealtime, reason);
mDischargeStartLevel = mHistoryCur.batteryLevel;
pullPendingStateUpdatesLocked();
addHistoryRecordLocked(mSecRealtime, mSecUptime);
@@ -15497,6 +15612,73 @@ public class BatteryStatsImpl extends BatteryStats {
return false;
}
/**
* Might reset battery stats if conditions are met. Assumed the device is currently plugged in.
*/
@GuardedBy("this")
public void maybeResetWhilePluggedInLocked() {
final long elapsedRealtimeMs = mClock.elapsedRealtime();
if (shouldResetWhilePluggedInLocked(elapsedRealtimeMs)) {
Slog.i(TAG,
"Resetting due to long plug in duration. elapsed time = " + elapsedRealtimeMs
+ " ms, last plug in time = " + mBatteryPluggedInRealTimeMs
+ " ms, last reset time = " + mRealtimeStartUs / 1000);
resetAllStatsAndHistoryLocked(RESET_REASON_PLUGGED_IN_FOR_LONG_DURATION);
}
scheduleNextResetWhilePluggedInCheck();
}
@GuardedBy("this")
private void scheduleNextResetWhilePluggedInCheck() {
if (mLongPlugInAlarmInterface != null) {
final long timeoutMs = mClock.currentTimeMillis()
+ mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
* DateUtils.HOUR_IN_MILLIS;
Calendar nextAlarm = Calendar.getInstance();
nextAlarm.setTimeInMillis(timeoutMs);
// Find the 2 AM the same day as the end of the minimum duration.
// This logic does not handle a Daylight Savings transition, or a timezone change
// while the alarm has been set. The need to reset after a long period while plugged
// in is not strict enough to warrant a well architected out solution.
nextAlarm.set(Calendar.MILLISECOND, 0);
nextAlarm.set(Calendar.SECOND, 0);
nextAlarm.set(Calendar.MINUTE, 0);
nextAlarm.set(Calendar.HOUR_OF_DAY, 2);
long nextTimeMs = nextAlarm.getTimeInMillis();
if (nextTimeMs < timeoutMs) {
// The 2AM on the day of the timeout, move on the next day.
nextTimeMs += DateUtils.DAY_IN_MILLIS;
}
mLongPlugInAlarmInterface.schedule(nextTimeMs, DateUtils.HOUR_IN_MILLIS);
}
}
@GuardedBy("this")
private boolean shouldResetWhilePluggedInLocked(long elapsedRealtimeMs) {
if (mNoAutoReset) return false;
if (!mSystemReady) return false;
final long pluggedInThresholdMs = mBatteryPluggedInRealTimeMs
+ mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
* DateUtils.HOUR_IN_MILLIS;
if (elapsedRealtimeMs >= pluggedInThresholdMs) {
// The device has been plugged in for a long time.
final long resetThresholdMs = mRealtimeStartUs / 1000
+ mConstants.RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS
* DateUtils.HOUR_IN_MILLIS;
if (elapsedRealtimeMs >= resetThresholdMs) {
// And it has been a long time since the last reset.
return true;
}
}
return false;
}
/**
* Notifies BatteryStatsImpl that the system server is ready.
*/
@@ -15504,6 +15686,32 @@ public class BatteryStatsImpl extends BatteryStats {
mSystemReady = true;
}
@GuardedBy("this")
private boolean shouldResetOnUnplugLocked(int batteryStatus, int batteryLevel) {
if (mNoAutoReset) return false;
if (!mSystemReady) return false;
if (mBatteryStatsConfig.shouldResetOnUnplugHighBatteryLevel()) {
// Allow resetting due to currently being at high battery level
if (batteryStatus == BatteryManager.BATTERY_STATUS_FULL) return true;
if (batteryLevel >= 90) return true;
}
if (mBatteryStatsConfig.shouldResetOnUnplugAfterSignificantCharge()) {
// Allow resetting after a significant charge (from a very low level to a now very
// high level).
if (mDischargePlugLevel < 20 && batteryLevel >= 80) return true;
}
if (getHighDischargeAmountSinceCharge() >= 200) {
// Reset the stats if battery got partially charged and discharged repeatedly without
// ever reaching the full charge.
// This reset is done in order to prevent stats sessions from going on forever.
// Exceedingly long battery sessions would lead to an overflow of
// data structures such as mWakeupReasonStats.
return true;
}
return false;
}
@GuardedBy("this")
protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
final boolean onBattery, final int oldStatus, final int level, final int chargeUah) {
@@ -15516,23 +15724,10 @@ public class BatteryStatsImpl extends BatteryStats {
final long realtimeUs = mSecRealtime * 1000;
final int screenState = mScreenState;
if (onBattery) {
// We will reset our status if we are unplugging after the
// battery was last full, or the level is at 100, or
// we have gone through a significant charge (from a very low
// level to a now very high level).
// Also, we will reset the stats if battery got partially charged
// and discharged repeatedly without ever reaching the full charge.
// This reset is done in order to prevent stats sessions from going on forever.
// Exceedingly long battery sessions would lead to an overflow of
// data structures such as mWakeupReasonStats.
boolean reset = false;
if (!mNoAutoReset && mSystemReady
&& (oldStatus == BatteryManager.BATTERY_STATUS_FULL
|| level >= 90
|| (mDischargeCurrentLevel < 20 && level >= 80)
|| getHighDischargeAmountSinceCharge() >= 200)) {
if (shouldResetOnUnplugLocked(oldStatus, level)) {
Slog.i(TAG, "Resetting battery stats: level=" + level + " status=" + oldStatus
+ " dischargeLevel=" + mDischargeCurrentLevel
+ " dischargeLevel=" + mDischargePlugLevel
+ " lowAmount=" + getLowDischargeAmountSinceCharge()
+ " highAmount=" + getHighDischargeAmountSinceCharge());
// Before we write, collect a snapshot of the final aggregated
@@ -15589,6 +15784,9 @@ public class BatteryStatsImpl extends BatteryStats {
mInitStepMode = mCurStepMode;
mModStepMode = 0;
pullPendingStateUpdatesLocked();
if (mLongPlugInAlarmInterface != null) {
mLongPlugInAlarmInterface.cancel();
}
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
@@ -15620,6 +15818,7 @@ public class BatteryStatsImpl extends BatteryStats {
mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = false;
pullPendingStateUpdatesLocked();
mBatteryPluggedInRealTimeMs = mSecRealtime;
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
@@ -15637,6 +15836,7 @@ public class BatteryStatsImpl extends BatteryStats {
mMaxChargeStepLevel = level;
mInitStepMode = mCurStepMode;
mModStepMode = 0;
scheduleNextResetWhilePluggedInCheck();
}
if (doWrite || (mLastWriteTimeMs + (60 * 1000)) < mSecRealtime) {
if (mStatsFile != null && mBatteryStatsHistory.getActiveFile() != null) {
@@ -16651,6 +16851,8 @@ public class BatteryStatsImpl extends BatteryStats {
public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
public static final String KEY_BATTERY_CHARGED_DELAY_MS =
"battery_charged_delay_ms";
public static final String KEY_RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS =
"reset_while_plugged_in_minimum_duration_hours";
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
@@ -16663,6 +16865,8 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
// Little less than 2 days
private static final int DEFAULT_RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS = 47;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
/* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
@@ -16678,6 +16882,8 @@ public class BatteryStatsImpl extends BatteryStats {
public int MAX_HISTORY_FILES;
public int MAX_HISTORY_BUFFER; /*Bytes*/
public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
public int RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS =
DEFAULT_RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -16754,6 +16960,11 @@ public class BatteryStatsImpl extends BatteryStats {
DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS = mParser.getInt(
KEY_RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS,
DEFAULT_RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS);
updateBatteryChargedDelayMsLocked();
}
}
@@ -16808,6 +17019,8 @@ public class BatteryStatsImpl extends BatteryStats {
pw.println(MAX_HISTORY_BUFFER/1024);
pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("=");
pw.println(BATTERY_CHARGED_DELAY_MS);
pw.print(KEY_RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS); pw.print("=");
pw.println(RESET_WHILE_PLUGGED_IN_MINIMUM_DURATION_HOURS);
}
}

View File

@@ -6092,4 +6092,9 @@
<!-- Whether to show weather on the lock screen by default. -->
<bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
<!-- Whether to reset Battery Stats on unplug when the battery level is high. -->
<bool name="config_batteryStatsResetOnUnplugHighBatteryLevel">true</bool>
<!-- Whether to reset Battery Stats on unplug if the battery was significantly charged -->
<bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool>
</resources>

View File

@@ -4903,4 +4903,7 @@
<!-- Whether to show weather on the lockscreen by default. -->
<java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
</resources>

View File

@@ -0,0 +1,362 @@
/*
* Copyright (C) 2023 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.internal.os;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.BatteryManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BatteryStatsResetTest {
private static final int BATTERY_NOMINAL_VOLTAGE_MV = 3700;
private static final int BATTERY_CAPACITY_UAH = 4_000_000;
private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100;
private MockClock mMockClock;
private MockBatteryStatsImpl mBatteryStatsImpl;
/**
* Battery status. Must be one of the following:
* {@link BatteryManager#BATTERY_STATUS_UNKNOWN}
* {@link BatteryManager#BATTERY_STATUS_CHARGING}
* {@link BatteryManager#BATTERY_STATUS_DISCHARGING}
* {@link BatteryManager#BATTERY_STATUS_NOT_CHARGING}
* {@link BatteryManager#BATTERY_STATUS_FULL}
*/
private int mBatteryStatus;
/**
* Battery health. Must be one of the following:
* {@link BatteryManager#BATTERY_HEALTH_UNKNOWN}
* {@link BatteryManager#BATTERY_HEALTH_GOOD}
* {@link BatteryManager#BATTERY_HEALTH_OVERHEAT}
* {@link BatteryManager#BATTERY_HEALTH_DEAD}
* {@link BatteryManager#BATTERY_HEALTH_OVER_VOLTAGE}
* {@link BatteryManager#BATTERY_HEALTH_UNSPECIFIED_FAILURE}
* {@link BatteryManager#BATTERY_HEALTH_COLD}
*/
private int mBatteryHealth;
/**
* Battery plug type. Can be the union of any number of the following flags:
* {@link BatteryManager#BATTERY_PLUGGED_AC}
* {@link BatteryManager#BATTERY_PLUGGED_USB}
* {@link BatteryManager#BATTERY_PLUGGED_WIRELESS}
* {@link BatteryManager#BATTERY_PLUGGED_DOCK}
*
* Zero means the device is unplugged.
*/
private int mBatteryPlugType;
private int mBatteryLevel;
private int mBatteryTemp;
private int mBatteryVoltageMv;
private int mBatteryChargeUah;
private int mBatteryChargeFullUah;
private long mBatteryChargeTimeToFullSeconds;
@Before
public void setUp() {
final Context context = InstrumentationRegistry.getContext();
mMockClock = new MockClock();
mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, context.getFilesDir());
mBatteryStatsImpl.onSystemReady();
// Set up the battery state. Start off with a fully charged plugged in battery.
mBatteryStatus = BatteryManager.BATTERY_STATUS_FULL;
mBatteryHealth = BatteryManager.BATTERY_HEALTH_GOOD;
mBatteryPlugType = BatteryManager.BATTERY_PLUGGED_USB;
mBatteryLevel = 100;
mBatteryTemp = 70; // Arbitrary reasonable temperature.
mBatteryVoltageMv = BATTERY_NOMINAL_VOLTAGE_MV;
mBatteryChargeUah = BATTERY_CAPACITY_UAH;
mBatteryChargeFullUah = BATTERY_CAPACITY_UAH;
mBatteryChargeTimeToFullSeconds = 0;
}
@Test
public void testResetOnUnplug_highBatteryLevel() {
mBatteryStatsImpl.setBatteryStatsConfig(
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(true)
.build());
long expectedResetTimeUs = 0;
unplugBattery();
dischargeToLevel(60);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(80);
unplugBattery();
// Reset should not occur until battery level above 90.
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(95);
// Reset should not occur until unplug.
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
unplugBattery();
// Reset should occur on unplug now that battery level is high (>=90)
expectedResetTimeUs = mMockClock.elapsedRealtime() * 1000;
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// disable high battery level reset on unplug.
mBatteryStatsImpl.setBatteryStatsConfig(
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(false)
.build());
dischargeToLevel(60);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(95);
unplugBattery();
// Reset should not occur since the high battery level logic has been disabled.
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
}
@Test
public void testResetOnUnplug_significantCharge() {
mBatteryStatsImpl.setBatteryStatsConfig(
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugAfterSignificantCharge(true)
.build());
long expectedResetTimeUs = 0;
unplugBattery();
// Battery level dropped below 20%.
dischargeToLevel(15);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(50);
unplugBattery();
// Reset should not occur until battery level above 80
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(85);
unplugBattery();
// Reset should not occur because the charge session did not go from 20% to 80%
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Battery level dropped below 20%.
dischargeToLevel(15);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(85);
unplugBattery();
// Reset should occur after significant charge amount.
expectedResetTimeUs = mMockClock.elapsedRealtime() * 1000;
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// disable reset on unplug after significant charge.
mBatteryStatsImpl.setBatteryStatsConfig(
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugAfterSignificantCharge(false)
.build());
// Battery level dropped below 20%.
dischargeToLevel(15);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(85);
unplugBattery();
// Reset should not occur after significant charge amount.
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
}
@Test
public void testResetOnUnplug_manyPartialCharges() {
long expectedResetTimeUs = 0;
unplugBattery();
// Cumulative battery discharged: 60%.
dischargeToLevel(40);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(80);
unplugBattery();
// Reset should not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Cumulative battery discharged: 100%.
dischargeToLevel(40);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(80);
unplugBattery();
// Reset should not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Cumulative battery discharged: 140%.
dischargeToLevel(40);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(80);
unplugBattery();
// Reset should not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Cumulative battery discharged: 180%.
dischargeToLevel(40);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(80);
unplugBattery();
// Reset should not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Cumulative battery discharged: 220%.
dischargeToLevel(40);
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
chargeToLevel(80);
unplugBattery();
// Should reset after >200% of cumulative battery discharge
expectedResetTimeUs = mMockClock.elapsedRealtime() * 1000;
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
}
@Test
public void testResetWhilePluggedIn_longPlugIn() {
// disable high battery level reset on unplug.
mBatteryStatsImpl.setBatteryStatsConfig(
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(false)
.setResetOnUnplugAfterSignificantCharge(false)
.build());
long expectedResetTimeUs = 0;
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset should not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Increment time a day
incTimeMs(24L * 60L * 60L * 1000L);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset should still not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Increment time a day
incTimeMs(24L * 60L * 60L * 1000L);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset 47 hour threshold crossed, reset should occur.
expectedResetTimeUs = mMockClock.elapsedRealtime() * 1000;
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Increment time a day
incTimeMs(24L * 60L * 60L * 1000L);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset should not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Increment time a day
incTimeMs(24L * 60L * 60L * 1000L);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset another 47 hour threshold crossed, reset should occur.
expectedResetTimeUs = mMockClock.elapsedRealtime() * 1000;
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Increment time a day
incTimeMs(24L * 60L * 60L * 1000L);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset should not occur
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
unplugBattery();
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
// Increment time a day
incTimeMs(24L * 60L * 60L * 1000L);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset should not occur, since unplug occurred recently.
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// Increment time a day
incTimeMs(24L * 60L * 60L * 1000L);
mBatteryStatsImpl.maybeResetWhilePluggedInLocked();
// Reset another 47 hour threshold crossed, reset should occur.
expectedResetTimeUs = mMockClock.elapsedRealtime() * 1000;
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
}
private void dischargeToLevel(int targetLevel) {
mBatteryStatus = BatteryManager.BATTERY_STATUS_DISCHARGING;
for (int level = mBatteryLevel - 1; level >= targetLevel; level--) {
prepareBatteryLevel(level);
incTimeMs(5000); // Arbitrary discharge rate.
updateBatteryState();
}
}
private void chargeToLevel(int targetLevel) {
mBatteryStatus = BatteryManager.BATTERY_STATUS_CHARGING;
for (int level = mBatteryLevel + 1; level <= targetLevel; level++) {
if (level >= 100) mBatteryStatus = BatteryManager.BATTERY_STATUS_FULL;
prepareBatteryLevel(level);
incTimeMs(BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL * 1000);
updateBatteryState();
}
}
private void unplugBattery() {
mBatteryPlugType = 0;
updateBatteryState();
}
private void plugBattery(int type) {
mBatteryPlugType |= type;
updateBatteryState();
}
private void prepareBatteryLevel(int level) {
mBatteryLevel = level;
mBatteryChargeUah = mBatteryLevel * mBatteryChargeFullUah / 100;
mBatteryChargeTimeToFullSeconds =
(100 - mBatteryLevel) * BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL;
}
private void incTimeMs(long milliseconds) {
mMockClock.realtime += milliseconds;
mMockClock.uptime += milliseconds / 2; // Arbitrary slower uptime accumulation
mMockClock.currentTime += milliseconds;
}
private void updateBatteryState() {
mBatteryStatsImpl.setBatteryStateLocked(mBatteryStatus, mBatteryHealth, mBatteryPlugType,
mBatteryLevel, mBatteryTemp, mBatteryVoltageMv, mBatteryChargeUah,
mBatteryChargeFullUah, mBatteryChargeTimeToFullSeconds,
mMockClock.elapsedRealtime(), mMockClock.uptimeMillis(),
mMockClock.currentTimeMillis());
}
}

View File

@@ -342,7 +342,7 @@ public class BatteryUsageStatsProviderTest {
Context context = InstrumentationRegistry.getContext();
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
mStatsRule.setCurrentTime(5 * MINUTE_IN_MS);
batteryStats.resetAllStatsCmdLocked();
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context,
batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"),
@@ -357,14 +357,14 @@ public class BatteryUsageStatsProviderTest {
batteryStats.noteFlashlightOffLocked(APP_UID,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
mStatsRule.setCurrentTime(25 * MINUTE_IN_MS);
batteryStats.resetAllStatsCmdLocked();
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
batteryStats.noteFlashlightOnLocked(APP_UID,
30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
batteryStats.noteFlashlightOffLocked(APP_UID,
50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
mStatsRule.setCurrentTime(55 * MINUTE_IN_MS);
batteryStats.resetAllStatsCmdLocked();
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
// This section should be ignored because the timestamp is out or range
batteryStats.noteFlashlightOnLocked(APP_UID,
@@ -372,7 +372,7 @@ public class BatteryUsageStatsProviderTest {
batteryStats.noteFlashlightOffLocked(APP_UID,
70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
mStatsRule.setCurrentTime(75 * MINUTE_IN_MS);
batteryStats.resetAllStatsCmdLocked();
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
// This section should be ignored because it represents the current stats session
batteryStats.noteFlashlightOnLocked(APP_UID,

View File

@@ -84,7 +84,7 @@ public class BatteryUsageStatsStoreTest {
mMockClock.realtime = 1_000_000;
mMockClock.uptime = 1_000_000;
mBatteryStats.resetAllStatsCmdLocked();
mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps();
assertThat(timestamps).hasLength(1);
@@ -114,7 +114,7 @@ public class BatteryUsageStatsStoreTest {
final int numberOfSnapshots =
(int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize);
for (int i = 0; i < numberOfSnapshots + 2; i++) {
mBatteryStats.resetAllStatsCmdLocked();
mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
mMockClock.realtime += 10_000_000;
mMockClock.uptime += 10_000_000;
@@ -141,7 +141,7 @@ public class BatteryUsageStatsStoreTest {
mMockClock.currentTime += 10_000_000;
prepareBatteryStats();
mBatteryStats.resetAllStatsCmdLocked();
mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0);

View File

@@ -21,6 +21,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
import android.annotation.NonNull;
import android.app.AlarmManager;
import android.app.StatsManager;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -356,6 +357,16 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
mStats.setPowerProfileLocked(mPowerProfile);
final boolean resetOnUnplugHighBatteryLevel = context.getResources().getBoolean(
com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel);
final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean(
com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge);
mStats.setBatteryStatsConfig(
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel)
.setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
.build());
mStats.startTrackingSystemServerCpuTime();
if (BATTERY_USAGE_STORE_ENABLED) {
@@ -386,6 +397,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub
Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
}
final AlarmManager am = mContext.getSystemService(AlarmManager.class);
mHandler.post(() -> {
synchronized (mStats) {
mStats.setLongPlugInAlarmInterface(new AlarmInterface(am, () -> {
synchronized (mStats) {
if (mStats.isOnBattery()) return;
mStats.maybeResetWhilePluggedInLocked();
}
}));
}
});
synchronized (mPowerStatsLock) {
mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
if (mPowerStatsInternal != null) {
@@ -2259,6 +2282,32 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
final class AlarmInterface implements BatteryStatsImpl.AlarmInterface,
AlarmManager.OnAlarmListener {
private AlarmManager mAm;
private Runnable mOnAlarm;
AlarmInterface(AlarmManager am, Runnable onAlarm) {
mAm = am;
mOnAlarm = onAlarm;
}
@Override
public void schedule(long rtcTimeMs, long windowLengthMs) {
mAm.setWindow(AlarmManager.RTC, rtcTimeMs, windowLengthMs, TAG, this, mHandler);
}
@Override
public void cancel() {
mAm.cancel(this);
}
@Override
public void onAlarm() {
mOnAlarm.run();
}
}
private static native int nativeWaitWakeup(ByteBuffer outBuffer);
private void dumpHelp(PrintWriter pw) {
@@ -2445,7 +2494,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("--reset-all".equals(arg)) {
awaitCompletion();
synchronized (mStats) {
mStats.resetAllStatsCmdLocked();
mStats.resetAllStatsAndHistoryLocked(
BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
mBatteryUsageStatsStore.removeAllSnapshots();
pw.println("Battery stats and history reset.");
noOutput = true;
@@ -2453,7 +2503,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("--reset".equals(arg)) {
awaitCompletion();
synchronized (mStats) {
mStats.resetAllStatsCmdLocked();
mStats.resetAllStatsAndHistoryLocked(
BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
pw.println("Battery stats reset.");
noOutput = true;
}