Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 88fbe863 authored by Michael Wachenschwanz's avatar Michael Wachenschwanz
Browse files

Reset BatteryStats when device has been plugged in for a long time.

BatteryStats continues to accumlate some stats while plugged in. The
avoid overflows or excessive memory usage, occasionally reset while
plugged in for extended periods of time.

Bug: 256919205
Test: atest BatteryStatsResetTest
Change-Id: If0db995ffaf4866bbadc828a0ade37a10da91553
Merged-In: If0db995ffaf4866bbadc828a0ade37a10da91553
parent ebd856f4
Loading
Loading
Loading
Loading
+113 −2
Original line number Diff line number Diff line
@@ -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;
@@ -462,6 +464,14 @@ public class BatteryStatsImpl extends BatteryStats {
    }
    /** 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() {
@@ -792,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;
@@ -1538,6 +1549,9 @@ public class BatteryStatsImpl extends BatteryStats {
    @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).
@@ -12636,6 +12650,18 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    /**
     * 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.
@@ -13108,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);
@@ -15586,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.
     */
@@ -15691,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: "
@@ -15722,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: "
@@ -15739,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) {
@@ -16753,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;
@@ -16765,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
@@ -16780,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(',');
@@ -16856,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();
            }
        }
@@ -16910,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);
        }
    }
+64 −0
Original line number Diff line number Diff line
@@ -245,6 +245,70 @@ public class BatteryStatsResetTest {
        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--) {
+4 −4
Original line number Diff line number Diff line
@@ -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,
+3 −3
Original line number Diff line number Diff line
@@ -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);
+43 −2
Original line number Diff line number Diff line
@@ -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;
@@ -396,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) {
@@ -2269,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) {
@@ -2455,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;
@@ -2463,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;
                    }