diff options
5 files changed, 227 insertions, 11 deletions
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 49537549a988..3a0e09d1fc77 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -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). @@ -12637,6 +12651,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); @@ -15587,6 +15613,73 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * 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. */ public void onSystemReady() { @@ -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); } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsResetTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsResetTest.java index cfe8d68b1416..9c2d332a7c70 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsResetTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsResetTest.java @@ -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--) { diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java index 274286135174..ae2d1afe0320 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -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, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java index c9729fab3b5e..11b9047fab7f 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java @@ -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); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c971d9221218..2f95716639f0 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -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; } |