From d576379b7da5b396e712505515a68478e095028f Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Thu, 5 May 2022 23:37:03 -0700 Subject: [PATCH] Don't auto move into restricted bucket if there's a recent interaction Adding a grace period here, which is, if the app background battery usage goes beyond the threshold, but the user interacts with it recently, don't move it to the restricted bucket level automatically. If it continues to drain the battery in the background after certain amount of time, it'll be restricted still. Bug: 203105544 Test: atest FrameworksMockingServicesTests:BackgroundRestrictionTest Change-Id: I8c86b399d3336e1fd71fcdfa25a93d1a6cd5c75f --- .../android/server/am/AppBatteryTracker.java | 106 +++++++++++++++--- .../server/am/BackgroundRestrictionTest.java | 46 ++++++++ 2 files changed, 138 insertions(+), 14 deletions(-) diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 6e28d8fbad592..c7e4f315db920 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -68,6 +68,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -361,6 +362,7 @@ final class AppBatteryTracker extends BaseAppStateTracker mUidBatteryUsageInWindow.removeAt(i); } } + mInjector.getPolicy().onUserRemovedLocked(userId); } } @@ -369,6 +371,7 @@ final class AppBatteryTracker extends BaseAppStateTracker synchronized (mLock) { mUidBatteryUsage.delete(uid); mUidBatteryUsageInWindow.delete(uid); + mInjector.getPolicy().onUidRemovedLocked(uid); } } @@ -1208,6 +1211,14 @@ final class AppBatteryTracker extends BaseAppStateTracker static final String KEY_BG_CURRENT_DRAIN_WINDOW = DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_window"; + /** + * The grace period after an interaction event with the app, if the background current + * drain goes beyond the threshold within that period, the system won't apply the + * restrictions. + */ + static final String KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_interaction_grace_period"; + /** * Similar to {@link #KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET}, but a higher * value for the legitimate cases with higher background current drain. @@ -1310,6 +1321,11 @@ final class AppBatteryTracker extends BaseAppStateTracker */ final long mDefaultBgCurrentDrainWindowMs; + /** + * Default value to {@link #mBgCurrentDrainInteractionGracePeriodMs}. + */ + final long mDefaultBgCurrentDrainInteractionGracePeriodMs; + /** * Default value to the {@link #INDEX_HIGH_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainRestrictedBucketThreshold}. @@ -1394,6 +1410,11 @@ final class AppBatteryTracker extends BaseAppStateTracker */ volatile long mBgCurrentDrainWindowMs; + /** + * @see #KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD. + */ + volatile long mBgCurrentDrainInteractionGracePeriodMs; + /** * @see #KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION. */ @@ -1456,6 +1477,12 @@ final class AppBatteryTracker extends BaseAppStateTracker private final SparseArray> mHighBgBatteryPackages = new SparseArray<>(); + /** + * The timestamp of the last interaction, key is the UID. + */ + @GuardedBy("mLock") + private final SparseLongArray mLastInteractionTime = new SparseLongArray(); + @NonNull private final Object mLock; @@ -1479,6 +1506,7 @@ final class AppBatteryTracker extends BaseAppStateTracker isLowRamDeviceStatic() ? val[1] : val[0]; mDefaultBgCurrentDrainWindowMs = resources.getInteger( R.integer.config_bg_current_drain_window) * 1_000; + mDefaultBgCurrentDrainInteractionGracePeriodMs = mDefaultBgCurrentDrainWindowMs; val = getFloatArray(resources.obtainTypedArray( R.array.config_bg_current_drain_high_threshold_to_restricted_bucket)); mDefaultBgCurrentDrainRestrictedBucketHighThreshold = @@ -1512,6 +1540,8 @@ final class AppBatteryTracker extends BaseAppStateTracker mBgCurrentDrainBgRestrictedThreshold[1] = mDefaultBgCurrentDrainBgRestrictedHighThreshold; mBgCurrentDrainWindowMs = mDefaultBgCurrentDrainWindowMs; + mBgCurrentDrainInteractionGracePeriodMs = + mDefaultBgCurrentDrainInteractionGracePeriodMs; mBgCurrentDrainMediaPlaybackMinDuration = mDefaultBgCurrentDrainMediaPlaybackMinDuration; mBgCurrentDrainLocationMinDuration = mDefaultBgCurrentDrainLocationMinDuration; @@ -1543,6 +1573,9 @@ final class AppBatteryTracker extends BaseAppStateTracker case KEY_BG_CURRENT_DRAIN_WINDOW: updateCurrentDrainWindow(); break; + case KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD: + updateCurrentDrainInteractionGracePeriod(); + break; case KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION: updateCurrentDrainMediaPlaybackMinDuration(); break; @@ -1627,6 +1660,13 @@ final class AppBatteryTracker extends BaseAppStateTracker mDefaultBgCurrentDrainWindowMs); } + private void updateCurrentDrainInteractionGracePeriod() { + mBgCurrentDrainInteractionGracePeriodMs = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD, + mDefaultBgCurrentDrainInteractionGracePeriodMs); + } + private void updateCurrentDrainMediaPlaybackMinDuration() { mBgCurrentDrainMediaPlaybackMinDuration = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -1669,6 +1709,7 @@ final class AppBatteryTracker extends BaseAppStateTracker super.onSystemReady(); updateCurrentDrainThreshold(); updateCurrentDrainWindow(); + updateCurrentDrainInteractionGracePeriod(); updateCurrentDrainMediaPlaybackMinDuration(); updateCurrentDrainLocationMinDuration(); updateCurrentDrainEventDurationBasedThresholdEnabled(); @@ -1686,8 +1727,10 @@ final class AppBatteryTracker extends BaseAppStateTracker synchronized (mLock) { final Pair pair = mHighBgBatteryPackages.get(uid); if (pair != null) { + final long lastInteractionTime = mLastInteractionTime.get(uid, 0L); final long[] ts = pair.first; - final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] > 0 + final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] + > (lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) && mTracker.mAppRestrictionController.isAutoRestrictAbusiveAppEnabled() ? RESTRICTION_LEVEL_RESTRICTED_BUCKET : RESTRICTION_LEVEL_ADAPTIVE_BUCKET; @@ -1778,6 +1821,7 @@ final class AppBatteryTracker extends BaseAppStateTracker // We're already in the background restricted level, nothing more we could do. return; } + final long lastInteractionTime = mLastInteractionTime.get(uid, 0L); final long now = SystemClock.elapsedRealtime(); final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now, mBgCurrentDrainWindowMs); @@ -1789,13 +1833,17 @@ final class AppBatteryTracker extends BaseAppStateTracker long[] ts = null; ImmutableBatteryUsage[] usages = null; if (rbPercentage >= rbThreshold) { - // New findings to us, track it and let the controller know. - ts = new long[TIME_STAMP_INDEX_LAST]; - ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; - usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST]; - usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; - mHighBgBatteryPackages.put(uid, Pair.create(ts, usages)); - notifyController = excessive = true; + if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) { + // New findings to us, track it and let the controller know. + ts = new long[TIME_STAMP_INDEX_LAST]; + ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; + usages = new ImmutableBatteryUsage[TIME_STAMP_INDEX_LAST]; + usages[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + mHighBgBatteryPackages.put(uid, Pair.create(ts, usages)); + // It's beeen long enough since last interaction with this app. + notifyController = true; + } + excessive = true; } if (decoupleThresholds && brPercentage >= brThreshold) { if (ts == null) { @@ -1813,11 +1861,15 @@ final class AppBatteryTracker extends BaseAppStateTracker final long[] ts = pair.first; final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]; if (rbPercentage >= rbThreshold) { - if (lastRestrictBucketTs == 0) { - ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; - pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + if (now > lastInteractionTime + mBgCurrentDrainInteractionGracePeriodMs) { + if (lastRestrictBucketTs == 0) { + ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; + pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + } + // It's been long enough since last interaction with this app. + notifyController = true; } - notifyController = excessive = true; + excessive = true; } else { // It's actually back to normal, but we don't untrack it until // explicit user interactions, because the restriction could be the cause @@ -1834,7 +1886,7 @@ final class AppBatteryTracker extends BaseAppStateTracker && (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs)); if (notifyController) { ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; - pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = usage; + pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = usage; } excessive = true; } else { @@ -1842,7 +1894,7 @@ final class AppBatteryTracker extends BaseAppStateTracker // user consent to unrestrict it; or if it's in restricted bucket level, // resetting this won't lift it from that level. ts[TIME_STAMP_INDEX_BG_RESTRICTED] = 0; - pair.second[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = null; + pair.second[TIME_STAMP_INDEX_BG_RESTRICTED] = null; // Now need to notify the controller. } } @@ -1903,6 +1955,7 @@ final class AppBatteryTracker extends BaseAppStateTracker void onUserInteractionStarted(String packageName, int uid) { boolean changed = false; synchronized (mLock) { + mLastInteractionTime.put(uid, SystemClock.elapsedRealtime()); final int curLevel = mTracker.mAppRestrictionController.getRestrictionLevel( uid, packageName); if (curLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { @@ -1941,9 +1994,30 @@ final class AppBatteryTracker extends BaseAppStateTracker @VisibleForTesting void reset() { mHighBgBatteryPackages.clear(); + mLastInteractionTime.clear(); mTracker.reset(); } + @GuardedBy("mLock") + void onUserRemovedLocked(final @UserIdInt int userId) { + for (int i = mHighBgBatteryPackages.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mHighBgBatteryPackages.keyAt(i)) == userId) { + mHighBgBatteryPackages.removeAt(i); + } + } + for (int i = mLastInteractionTime.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mLastInteractionTime.keyAt(i)) == userId) { + mLastInteractionTime.removeAt(i); + } + } + } + + @GuardedBy("mLock") + void onUidRemovedLocked(final int uid) { + mHighBgBatteryPackages.remove(uid); + mLastInteractionTime.delete(uid); + } + @Override void dump(PrintWriter pw, String prefix) { pw.print(prefix); @@ -1977,6 +2051,10 @@ final class AppBatteryTracker extends BaseAppStateTracker pw.print('='); pw.println(mBgCurrentDrainWindowMs); pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD); + pw.print('='); + pw.println(mBgCurrentDrainInteractionGracePeriodMs); + pw.print(prefix); pw.print(KEY_BG_CURRENT_DRAIN_MEDIA_PLAYBACK_MIN_DURATION); pw.print('='); pw.println(mBgCurrentDrainMediaPlaybackMinDuration); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index ac542935fa0a3..009dae51e94b6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -582,6 +582,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession bgCurrentDrainMonitor = null; DeviceConfigSession bgCurrentDrainWindow = null; + DeviceConfigSession bgCurrentDrainInteractionGracePeriod = null; DeviceConfigSession bgCurrentDrainRestrictedBucketThreshold = null; DeviceConfigSession bgCurrentDrainBgRestrictedThreshold = null; DeviceConfigSession bgPromptFgsWithNotiToBgRestricted = null; @@ -617,6 +618,14 @@ public final class BackgroundRestrictionTest { R.integer.config_bg_current_drain_window)); bgCurrentDrainWindow.set(windowMs); + bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD, + DeviceConfig::getLong, + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_window)); + bgCurrentDrainInteractionGracePeriod.set(windowMs); + bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET, @@ -768,6 +777,32 @@ public final class BackgroundRestrictionTest { clearInvocations(mInjector.getAppStandbyInternal()); + // It won't be restricted since user just interacted with it. + runTestBgCurrentDrainMonitorOnce(listener, stats, uids, + zeros, new double[]{0, restrictBucketThresholdMah - 1}, + zeros, new double[]{restrictBucketThresholdMah + 1, 0}, + () -> { + doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); + doReturn(mCurrentTimeMillis + windowMs) + .when(stats).getStatsEndTimestamp(); + mCurrentTimeMillis += windowMs + 1; + try { + listener.verify(timeout, testUid, testPkgName, + RESTRICTION_LEVEL_RESTRICTED_BUCKET); + fail("There shouldn't be any level change events"); + } catch (Exception e) { + // Expected. + } + verify(mInjector.getAppStandbyInternal(), never()).restrictApp( + eq(testPkgName), + eq(testUser), + anyInt(), anyInt()); + }); + + // Sleep a while. + Thread.sleep(windowMs); + clearInvocations(mInjector.getAppStandbyInternal()); + // Now it should have been restricted. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, zeros, new double[]{0, restrictBucketThresholdMah - 1}, zeros, new double[]{restrictBucketThresholdMah + 1, 0}, @@ -1061,6 +1096,7 @@ public final class BackgroundRestrictionTest { } finally { closeIfNotNull(bgCurrentDrainMonitor); closeIfNotNull(bgCurrentDrainWindow); + closeIfNotNull(bgCurrentDrainInteractionGracePeriod); closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold); closeIfNotNull(bgCurrentDrainBgRestrictedThreshold); closeIfNotNull(bgPromptFgsWithNotiToBgRestricted); @@ -1610,6 +1646,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession bgCurrentDrainMonitor = null; DeviceConfigSession bgCurrentDrainWindow = null; + DeviceConfigSession bgCurrentDrainInteractionGracePeriod = null; DeviceConfigSession bgCurrentDrainRestrictedBucketThreshold = null; DeviceConfigSession bgCurrentDrainBgRestrictedThreshold = null; DeviceConfigSession bgCurrentDrainRestrictedBucketHighThreshold = null; @@ -1655,6 +1692,14 @@ public final class BackgroundRestrictionTest { R.integer.config_bg_current_drain_window)); bgCurrentDrainWindow.set(windowMs); + bgCurrentDrainInteractionGracePeriod = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_INTERACTION_GRACE_PERIOD, + DeviceConfig::getLong, + (long) mContext.getResources().getInteger( + R.integer.config_bg_current_drain_window)); + bgCurrentDrainInteractionGracePeriod.set(windowMs); + bgCurrentDrainRestrictedBucketThreshold = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET, @@ -2176,6 +2221,7 @@ public final class BackgroundRestrictionTest { } finally { closeIfNotNull(bgCurrentDrainMonitor); closeIfNotNull(bgCurrentDrainWindow); + closeIfNotNull(bgCurrentDrainInteractionGracePeriod); closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold); closeIfNotNull(bgCurrentDrainBgRestrictedThreshold); closeIfNotNull(bgCurrentDrainRestrictedBucketHighThreshold);