Loading services/core/java/com/android/server/am/AppBatteryTracker.java +89 −29 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN; import static android.app.ActivityManager.isLowRamDeviceStatic; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; Loading Loading @@ -1118,6 +1119,13 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION = DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_by_bg_location"; /** * Whether or not the battery usage of the offending app should fulfill the 1st threshold * before taking actions for the 2nd threshold. */ static final String KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS = DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_decouple_thresholds"; /** * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainRestrictedBucketThreshold}. Loading Loading @@ -1190,6 +1198,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> */ final boolean mDefaultBgCurrentDrainHighThresholdByBgLocation; /** * Default value to {@link #mBgCurrentDrainDecoupleThresholds}. */ static final boolean DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD = true; /** * The index to {@link #mBgCurrentDrainRestrictedBucketThreshold} * and {@link #mBgCurrentDrainBgRestrictedThreshold}. Loading Loading @@ -1257,6 +1270,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> */ volatile boolean mBgCurrentDrainHighThresholdByBgLocation; /** * @see #KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS. */ volatile boolean mBgCurrentDrainDecoupleThresholds; /** * The capacity of the battery when fully charged in mAh. */ Loading Loading @@ -1369,6 +1387,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> case KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES: updateCurrentDrainExemptedTypes(); break; case KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS: updateCurrentDrainDecoupleThresholds(); break; default: super.onPropertiesChanged(name); break; Loading Loading @@ -1466,6 +1487,13 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mDefaultBgCurrentDrainExemptedTypes); } private void updateCurrentDrainDecoupleThresholds() { mBgCurrentDrainDecoupleThresholds = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS, DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD); } @Override public void onSystemReady() { mBatteryFullChargeMah = Loading @@ -1477,20 +1505,30 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> updateCurrentDrainLocationMinDuration(); updateCurrentDrainEventDurationBasedThresholdEnabled(); updateCurrentDrainExemptedTypes(); updateCurrentDrainDecoupleThresholds(); } @Override public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) { synchronized (mLock) { final int index = mHighBgBatteryPackages.indexOfKey(uid); if (index < 0) { // Not found, return adaptive as the default one. return RESTRICTION_LEVEL_ADAPTIVE_BUCKET; @RestrictionLevel public int getProposedRestrictionLevel(String packageName, int uid, @RestrictionLevel int maxLevel) { if (maxLevel <= RESTRICTION_LEVEL_ADAPTIVE_BUCKET) { return RESTRICTION_LEVEL_UNKNOWN; } final long[] ts = mHighBgBatteryPackages.valueAt(index); synchronized (mLock) { final long[] ts = mHighBgBatteryPackages.get(uid); if (ts != null) { final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] > 0 ? RESTRICTION_LEVEL_RESTRICTED_BUCKET : RESTRICTION_LEVEL_ADAPTIVE_BUCKET; if (maxLevel > RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { return ts[TIME_STAMP_INDEX_BG_RESTRICTED] > 0 ? RESTRICTION_LEVEL_BACKGROUND_RESTRICTED : RESTRICTION_LEVEL_RESTRICTED_BUCKET; ? RESTRICTION_LEVEL_BACKGROUND_RESTRICTED : restrictedLevel; } else if (maxLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { return restrictedLevel; } } return RESTRICTION_LEVEL_ADAPTIVE_BUCKET; } } Loading Loading @@ -1573,36 +1611,58 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now, mBgCurrentDrainWindowMs); final int index = mHighBgBatteryPackages.indexOfKey(uid); final boolean decoupleThresholds = mBgCurrentDrainDecoupleThresholds; final double rbThreshold = mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]; final double brThreshold = mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]; if (index < 0) { if (rbPercentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { long[] ts = null; if (rbPercentage >= rbThreshold) { // New findings to us, track it and let the controller know. final long[] ts = new long[TIME_STAMP_INDEX_LAST]; ts = new long[TIME_STAMP_INDEX_LAST]; ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; mHighBgBatteryPackages.put(uid, ts); notifyController = excessive = true; } if (decoupleThresholds && brPercentage >= brThreshold) { if (ts == null) { ts = new long[TIME_STAMP_INDEX_LAST]; mHighBgBatteryPackages.put(uid, ts); } ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; notifyController = excessive = true; } } else { final long[] ts = mHighBgBatteryPackages.valueAt(index); if (rbPercentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { // it's actually back to normal, but we don't untrack it until // explicit user interactions. notifyController = true; final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]; if (rbPercentage >= rbThreshold) { if (lastRestrictBucketTs == 0) { ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; } notifyController = excessive = true; } else { excessive = true; if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex] && curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { // If we're in the restricted standby bucket but still seeing high // current drains, tell the controller again. final long lastResbucket = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]; final long lastBgRes = ts[TIME_STAMP_INDEX_BG_RESTRICTED]; // If it has been a while since restricting the app and since the last // time we notify the controller, notify it again. if ((now >= lastResbucket + mBgCurrentDrainWindowMs) && (lastBgRes == 0 || (now >= lastBgRes + mBgCurrentDrainWindowMs))) { // It's actually back to normal, but we don't untrack it until // explicit user interactions, because the restriction could be the cause // of going back to normal. } if (brPercentage >= brThreshold) { // If either // a) It's configured to goto threshold 2 directly without threshold 1; // b) It's already in the restricted standby bucket, but still seeing // high current drains, and it's been a while since it's restricted; // tell the controller. notifyController = decoupleThresholds || (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET && (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs)); if (notifyController) { ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; notifyController = true; } } excessive = true; } else { // Reset the track now - if it's already background restricted, it requires // 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; // Now need to notify the controller. } } } Loading services/core/java/com/android/server/am/AppRestrictionController.java +15 −13 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION; import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN; import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; Loading Loading @@ -1152,7 +1153,8 @@ public final class AppRestrictionController { ? RESTRICTION_LEVEL_RESTRICTED_BUCKET : RESTRICTION_LEVEL_ADAPTIVE_BUCKET; if (calcTrackers) { @RestrictionLevel int l = calcAppRestrictionLevelFromTackers(uid, packageName); @RestrictionLevel int l = calcAppRestrictionLevelFromTackers(uid, packageName, RESTRICTION_LEVEL_MAX); if (l == RESTRICTION_LEVEL_EXEMPTED) { return RESTRICTION_LEVEL_EXEMPTED; } Loading @@ -1164,7 +1166,8 @@ public final class AppRestrictionController { uid, 0, packageName).sendToTarget(); } // Lower the level. level = RESTRICTION_LEVEL_RESTRICTED_BUCKET; level = calcAppRestrictionLevelFromTackers(uid, packageName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); } } break; Loading @@ -1181,12 +1184,13 @@ public final class AppRestrictionController { * monitors certain dimensions of the app, the abusive behaviors could be detected in one or * more of these dimensions, but not necessarily all of them. </p> */ private @RestrictionLevel int calcAppRestrictionLevelFromTackers(int uid, String packageName) { private @RestrictionLevel int calcAppRestrictionLevelFromTackers(int uid, String packageName, @RestrictionLevel int maxLevel) { @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN; final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled; for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) { @RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy() .getProposedRestrictionLevel(packageName, uid); .getProposedRestrictionLevel(packageName, uid, maxLevel); if (!isRestrictedBucketEnabled && l == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET; } Loading Loading @@ -1475,7 +1479,6 @@ public final class AppRestrictionController { } else if (curLevel >= RESTRICTION_LEVEL_RESTRICTED_BUCKET && level < RESTRICTION_LEVEL_RESTRICTED_BUCKET) { // Moved out of the background-restricted state. if (curBucket != STANDBY_BUCKET_RARE) { synchronized (mSettingsLock) { final int index = mActiveUids.indexOfKey(uid, pkgName); if (index >= 0) { Loading @@ -1487,7 +1490,6 @@ public final class AppRestrictionController { reason, subReason); } } } private void handleBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) { // Firstly, notify the trackers. Loading services/core/java/com/android/server/am/BaseAppStatePolicy.java +4 −2 Original line number Diff line number Diff line Loading @@ -87,9 +87,11 @@ public abstract class BaseAppStatePolicy<T extends BaseAppStateTracker> { } /** * @return The proposed background restriction policy for the given package/uid. * @return The proposed background restriction policy for the given package/uid, * the returned level should be capped at {@code maxLevel} (exclusive). */ public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) { public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid, @RestrictionLevel int maxLevel) { return RESTRICTION_LEVEL_UNKNOWN; } Loading services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java +11 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE; Loading Loading @@ -295,11 +296,19 @@ abstract class BaseAppStateTimeSlotEventsTracker } @Override public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) { @RestrictionLevel public int getProposedRestrictionLevel(String packageName, int uid, @RestrictionLevel int maxLevel) { synchronized (mLock) { return mExcessiveEventPkgs.get(packageName, uid) == null final int level = mExcessiveEventPkgs.get(packageName, uid) == null ? RESTRICTION_LEVEL_ADAPTIVE_BUCKET : RESTRICTION_LEVEL_RESTRICTED_BUCKET; if (maxLevel > RESTRICTION_LEVEL_RESTRICTED_BUCKET) { return level; } else if (maxLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { return RESTRICTION_LEVEL_ADAPTIVE_BUCKET; } return RESTRICTION_LEVEL_UNKNOWN; } } Loading services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +121 −0 Original line number Diff line number Diff line Loading @@ -579,6 +579,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null; DeviceConfigSession<Long> bgNotificationMinInterval = null; DeviceConfigSession<Integer> bgBatteryExemptionTypes = null; DeviceConfigSession<Boolean> bgCurrentDrainDecoupleThresholds = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); Loading Loading @@ -648,6 +649,13 @@ public final class BackgroundRestrictionTest { R.integer.config_bg_current_drain_exempted_types)); bgBatteryExemptionTypes.set(0); bgCurrentDrainDecoupleThresholds = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS, DeviceConfig::getBoolean, AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD); bgCurrentDrainDecoupleThresholds.set(true); mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); Loading Loading @@ -785,6 +793,11 @@ public final class BackgroundRestrictionTest { anyInt(), anyInt()); }); // Pretend we have the standby buckets set above. doReturn(STANDBY_BUCKET_RESTRICTED) .when(mAppStandbyInternal) .getAppStandbyBucket(eq(testPkgName), eq(testUser), anyLong(), anyBoolean()); // Sleep a while and set a higher drain Thread.sleep(windowMs); clearInvocations(mInjector.getAppStandbyInternal()); Loading Loading @@ -921,6 +934,11 @@ public final class BackgroundRestrictionTest { // Expected. } // Reset the standby bucket. doReturn(STANDBY_BUCKET_RARE) .when(mAppStandbyInternal) .getAppStandbyBucket(eq(testPkgName), eq(testUser), anyLong(), anyBoolean()); // Turn OFF the FAS. listener.mLatchHolder[0] = new CountDownLatch(1); clearInvocations(mInjector.getAppStandbyInternal()); Loading @@ -930,6 +948,99 @@ public final class BackgroundRestrictionTest { // It'll go back to restricted bucket because it used to behave poorly. listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_RESTRICTED_BUCKET); verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET, testPkgName, testUid); clearInvocations(mInjector.getAppStandbyInternal()); // Trigger user interaction. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{restrictBucketThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, () -> { doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis + windowMs) .when(stats).getStatsEndTimestamp(); mCurrentTimeMillis += windowMs + 1; mIdleStateListener.onUserInteractionStarted(testPkgName, testUser); waitForIdleHandler(mBgRestrictionController.getBackgroundHandler()); // It should have been back to normal. listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_ADAPTIVE_BUCKET); verify(mInjector.getAppStandbyInternal(), atLeast(1)).maybeUnrestrictApp( eq(testPkgName), eq(testUser), eq(REASON_MAIN_USAGE), eq(REASON_SUB_USAGE_USER_INTERACTION), eq(REASON_MAIN_USAGE), eq(REASON_SUB_USAGE_USER_INTERACTION)); }); bgCurrentDrainDecoupleThresholds.set(true); clearInvocations(mInjector.getAppStandbyInternal()); // Go to the threshold right away. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{0, restrictBucketThresholdMah - 1}, new double[]{bgRestrictedThresholdMah + 1, 0}, zeros, zeros, () -> { doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis + windowMs) .when(stats).getStatsEndTimestamp(); mCurrentTimeMillis += windowMs + 1; // We won't change restriction level automatically because it needs // user consent. try { listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); fail("There shouldn't be level change event like this"); } catch (Exception e) { // Expected. } verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket( eq(testPkgName), eq(STANDBY_BUCKET_RARE), eq(testUser), anyInt(), anyInt()); // We should have requested to goto background restricted level. verify(mBgRestrictionController, times(1)).handleRequestBgRestricted( eq(testPkgName), eq(testUid)); // Verify we have the notification posted now because its FGS is invisible. checkNotificationShown(new String[] {testPkgName}, atLeast(1), true); }); bgCurrentDrainDecoupleThresholds.set(false); clearInvocations(mInjector.getAppStandbyInternal()); clearInvocations(mBgRestrictionController); // Go to the threshold right away, but this time, it shouldn't even request to goto // bg restricted level because it requires to be in restricted bucket before that. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{0, restrictBucketThresholdMah - 1}, new double[]{bgRestrictedThresholdMah + 1, 0}, zeros, zeros, () -> { doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis + windowMs) .when(stats).getStatsEndTimestamp(); mCurrentTimeMillis += windowMs + 1; // We won't change restriction level automatically because it needs // user consent. try { listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); fail("There shouldn't be level change event like this"); } catch (Exception e) { // Expected. } verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket( eq(testPkgName), eq(STANDBY_BUCKET_RARE), eq(testUser), anyInt(), anyInt()); // We should NOT have requested to goto background restricted level. verify(mBgRestrictionController, never()).handleRequestBgRestricted( eq(testPkgName), eq(testUid)); }); } finally { closeIfNotNull(bgCurrentDrainMonitor); closeIfNotNull(bgCurrentDrainWindow); Loading @@ -938,6 +1049,7 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgPromptFgsWithNotiToBgRestricted); closeIfNotNull(bgNotificationMinInterval); closeIfNotNull(bgBatteryExemptionTypes); closeIfNotNull(bgCurrentDrainDecoupleThresholds); } } Loading Loading @@ -1441,6 +1553,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Boolean> bgPermissionMonitorEnabled = null; DeviceConfigSession<String> bgPermissionsInMonitor = null; DeviceConfigSession<Boolean> bgCurrentDrainHighThresholdByBgLocation = null; DeviceConfigSession<Boolean> bgCurrentDrainDecoupleThresholds = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); Loading Loading @@ -1572,6 +1685,13 @@ public final class BackgroundRestrictionTest { R.bool.config_bg_current_drain_high_threshold_by_bg_location)); bgCurrentDrainHighThresholdByBgLocation.set(true); bgCurrentDrainDecoupleThresholds = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS, DeviceConfig::getBoolean, AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD); bgCurrentDrainDecoupleThresholds.set(true); mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); Loading Loading @@ -1990,6 +2110,7 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgPermissionMonitorEnabled); closeIfNotNull(bgPermissionsInMonitor); closeIfNotNull(bgCurrentDrainHighThresholdByBgLocation); closeIfNotNull(bgCurrentDrainDecoupleThresholds); } } Loading Loading
services/core/java/com/android/server/am/AppBatteryTracker.java +89 −29 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN; import static android.app.ActivityManager.isLowRamDeviceStatic; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; Loading Loading @@ -1118,6 +1119,13 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION = DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_by_bg_location"; /** * Whether or not the battery usage of the offending app should fulfill the 1st threshold * before taking actions for the 2nd threshold. */ static final String KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS = DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_decouple_thresholds"; /** * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainRestrictedBucketThreshold}. Loading Loading @@ -1190,6 +1198,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> */ final boolean mDefaultBgCurrentDrainHighThresholdByBgLocation; /** * Default value to {@link #mBgCurrentDrainDecoupleThresholds}. */ static final boolean DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD = true; /** * The index to {@link #mBgCurrentDrainRestrictedBucketThreshold} * and {@link #mBgCurrentDrainBgRestrictedThreshold}. Loading Loading @@ -1257,6 +1270,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> */ volatile boolean mBgCurrentDrainHighThresholdByBgLocation; /** * @see #KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS. */ volatile boolean mBgCurrentDrainDecoupleThresholds; /** * The capacity of the battery when fully charged in mAh. */ Loading Loading @@ -1369,6 +1387,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> case KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES: updateCurrentDrainExemptedTypes(); break; case KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS: updateCurrentDrainDecoupleThresholds(); break; default: super.onPropertiesChanged(name); break; Loading Loading @@ -1466,6 +1487,13 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mDefaultBgCurrentDrainExemptedTypes); } private void updateCurrentDrainDecoupleThresholds() { mBgCurrentDrainDecoupleThresholds = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS, DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD); } @Override public void onSystemReady() { mBatteryFullChargeMah = Loading @@ -1477,20 +1505,30 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> updateCurrentDrainLocationMinDuration(); updateCurrentDrainEventDurationBasedThresholdEnabled(); updateCurrentDrainExemptedTypes(); updateCurrentDrainDecoupleThresholds(); } @Override public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) { synchronized (mLock) { final int index = mHighBgBatteryPackages.indexOfKey(uid); if (index < 0) { // Not found, return adaptive as the default one. return RESTRICTION_LEVEL_ADAPTIVE_BUCKET; @RestrictionLevel public int getProposedRestrictionLevel(String packageName, int uid, @RestrictionLevel int maxLevel) { if (maxLevel <= RESTRICTION_LEVEL_ADAPTIVE_BUCKET) { return RESTRICTION_LEVEL_UNKNOWN; } final long[] ts = mHighBgBatteryPackages.valueAt(index); synchronized (mLock) { final long[] ts = mHighBgBatteryPackages.get(uid); if (ts != null) { final int restrictedLevel = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] > 0 ? RESTRICTION_LEVEL_RESTRICTED_BUCKET : RESTRICTION_LEVEL_ADAPTIVE_BUCKET; if (maxLevel > RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { return ts[TIME_STAMP_INDEX_BG_RESTRICTED] > 0 ? RESTRICTION_LEVEL_BACKGROUND_RESTRICTED : RESTRICTION_LEVEL_RESTRICTED_BUCKET; ? RESTRICTION_LEVEL_BACKGROUND_RESTRICTED : restrictedLevel; } else if (maxLevel == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { return restrictedLevel; } } return RESTRICTION_LEVEL_ADAPTIVE_BUCKET; } } Loading Loading @@ -1573,36 +1611,58 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> final int thresholdIndex = getCurrentDrainThresholdIndex(uid, now, mBgCurrentDrainWindowMs); final int index = mHighBgBatteryPackages.indexOfKey(uid); final boolean decoupleThresholds = mBgCurrentDrainDecoupleThresholds; final double rbThreshold = mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]; final double brThreshold = mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]; if (index < 0) { if (rbPercentage >= mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { long[] ts = null; if (rbPercentage >= rbThreshold) { // New findings to us, track it and let the controller know. final long[] ts = new long[TIME_STAMP_INDEX_LAST]; ts = new long[TIME_STAMP_INDEX_LAST]; ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; mHighBgBatteryPackages.put(uid, ts); notifyController = excessive = true; } if (decoupleThresholds && brPercentage >= brThreshold) { if (ts == null) { ts = new long[TIME_STAMP_INDEX_LAST]; mHighBgBatteryPackages.put(uid, ts); } ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; notifyController = excessive = true; } } else { final long[] ts = mHighBgBatteryPackages.valueAt(index); if (rbPercentage < mBgCurrentDrainRestrictedBucketThreshold[thresholdIndex]) { // it's actually back to normal, but we don't untrack it until // explicit user interactions. notifyController = true; final long lastRestrictBucketTs = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]; if (rbPercentage >= rbThreshold) { if (lastRestrictBucketTs == 0) { ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] = now; } notifyController = excessive = true; } else { excessive = true; if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex] && curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { // If we're in the restricted standby bucket but still seeing high // current drains, tell the controller again. final long lastResbucket = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]; final long lastBgRes = ts[TIME_STAMP_INDEX_BG_RESTRICTED]; // If it has been a while since restricting the app and since the last // time we notify the controller, notify it again. if ((now >= lastResbucket + mBgCurrentDrainWindowMs) && (lastBgRes == 0 || (now >= lastBgRes + mBgCurrentDrainWindowMs))) { // It's actually back to normal, but we don't untrack it until // explicit user interactions, because the restriction could be the cause // of going back to normal. } if (brPercentage >= brThreshold) { // If either // a) It's configured to goto threshold 2 directly without threshold 1; // b) It's already in the restricted standby bucket, but still seeing // high current drains, and it's been a while since it's restricted; // tell the controller. notifyController = decoupleThresholds || (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET && (now > lastRestrictBucketTs + mBgCurrentDrainWindowMs)); if (notifyController) { ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; notifyController = true; } } excessive = true; } else { // Reset the track now - if it's already background restricted, it requires // 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; // Now need to notify the controller. } } } Loading
services/core/java/com/android/server/am/AppRestrictionController.java +15 −13 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION; import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN; import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; Loading Loading @@ -1152,7 +1153,8 @@ public final class AppRestrictionController { ? RESTRICTION_LEVEL_RESTRICTED_BUCKET : RESTRICTION_LEVEL_ADAPTIVE_BUCKET; if (calcTrackers) { @RestrictionLevel int l = calcAppRestrictionLevelFromTackers(uid, packageName); @RestrictionLevel int l = calcAppRestrictionLevelFromTackers(uid, packageName, RESTRICTION_LEVEL_MAX); if (l == RESTRICTION_LEVEL_EXEMPTED) { return RESTRICTION_LEVEL_EXEMPTED; } Loading @@ -1164,7 +1166,8 @@ public final class AppRestrictionController { uid, 0, packageName).sendToTarget(); } // Lower the level. level = RESTRICTION_LEVEL_RESTRICTED_BUCKET; level = calcAppRestrictionLevelFromTackers(uid, packageName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); } } break; Loading @@ -1181,12 +1184,13 @@ public final class AppRestrictionController { * monitors certain dimensions of the app, the abusive behaviors could be detected in one or * more of these dimensions, but not necessarily all of them. </p> */ private @RestrictionLevel int calcAppRestrictionLevelFromTackers(int uid, String packageName) { private @RestrictionLevel int calcAppRestrictionLevelFromTackers(int uid, String packageName, @RestrictionLevel int maxLevel) { @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN; final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled; for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) { @RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy() .getProposedRestrictionLevel(packageName, uid); .getProposedRestrictionLevel(packageName, uid, maxLevel); if (!isRestrictedBucketEnabled && l == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET; } Loading Loading @@ -1475,7 +1479,6 @@ public final class AppRestrictionController { } else if (curLevel >= RESTRICTION_LEVEL_RESTRICTED_BUCKET && level < RESTRICTION_LEVEL_RESTRICTED_BUCKET) { // Moved out of the background-restricted state. if (curBucket != STANDBY_BUCKET_RARE) { synchronized (mSettingsLock) { final int index = mActiveUids.indexOfKey(uid, pkgName); if (index >= 0) { Loading @@ -1487,7 +1490,6 @@ public final class AppRestrictionController { reason, subReason); } } } private void handleBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) { // Firstly, notify the trackers. Loading
services/core/java/com/android/server/am/BaseAppStatePolicy.java +4 −2 Original line number Diff line number Diff line Loading @@ -87,9 +87,11 @@ public abstract class BaseAppStatePolicy<T extends BaseAppStateTracker> { } /** * @return The proposed background restriction policy for the given package/uid. * @return The proposed background restriction policy for the given package/uid, * the returned level should be capped at {@code maxLevel} (exclusive). */ public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) { public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid, @RestrictionLevel int maxLevel) { return RESTRICTION_LEVEL_UNKNOWN; } Loading
services/core/java/com/android/server/am/BaseAppStateTimeSlotEventsTracker.java +11 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN; import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE; Loading Loading @@ -295,11 +296,19 @@ abstract class BaseAppStateTimeSlotEventsTracker } @Override public @RestrictionLevel int getProposedRestrictionLevel(String packageName, int uid) { @RestrictionLevel public int getProposedRestrictionLevel(String packageName, int uid, @RestrictionLevel int maxLevel) { synchronized (mLock) { return mExcessiveEventPkgs.get(packageName, uid) == null final int level = mExcessiveEventPkgs.get(packageName, uid) == null ? RESTRICTION_LEVEL_ADAPTIVE_BUCKET : RESTRICTION_LEVEL_RESTRICTED_BUCKET; if (maxLevel > RESTRICTION_LEVEL_RESTRICTED_BUCKET) { return level; } else if (maxLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { return RESTRICTION_LEVEL_ADAPTIVE_BUCKET; } return RESTRICTION_LEVEL_UNKNOWN; } } Loading
services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +121 −0 Original line number Diff line number Diff line Loading @@ -579,6 +579,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null; DeviceConfigSession<Long> bgNotificationMinInterval = null; DeviceConfigSession<Integer> bgBatteryExemptionTypes = null; DeviceConfigSession<Boolean> bgCurrentDrainDecoupleThresholds = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); Loading Loading @@ -648,6 +649,13 @@ public final class BackgroundRestrictionTest { R.integer.config_bg_current_drain_exempted_types)); bgBatteryExemptionTypes.set(0); bgCurrentDrainDecoupleThresholds = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS, DeviceConfig::getBoolean, AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD); bgCurrentDrainDecoupleThresholds.set(true); mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); Loading Loading @@ -785,6 +793,11 @@ public final class BackgroundRestrictionTest { anyInt(), anyInt()); }); // Pretend we have the standby buckets set above. doReturn(STANDBY_BUCKET_RESTRICTED) .when(mAppStandbyInternal) .getAppStandbyBucket(eq(testPkgName), eq(testUser), anyLong(), anyBoolean()); // Sleep a while and set a higher drain Thread.sleep(windowMs); clearInvocations(mInjector.getAppStandbyInternal()); Loading Loading @@ -921,6 +934,11 @@ public final class BackgroundRestrictionTest { // Expected. } // Reset the standby bucket. doReturn(STANDBY_BUCKET_RARE) .when(mAppStandbyInternal) .getAppStandbyBucket(eq(testPkgName), eq(testUser), anyLong(), anyBoolean()); // Turn OFF the FAS. listener.mLatchHolder[0] = new CountDownLatch(1); clearInvocations(mInjector.getAppStandbyInternal()); Loading @@ -930,6 +948,99 @@ public final class BackgroundRestrictionTest { // It'll go back to restricted bucket because it used to behave poorly. listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_RESTRICTED_BUCKET); verifyRestrictionLevel(RESTRICTION_LEVEL_RESTRICTED_BUCKET, testPkgName, testUid); clearInvocations(mInjector.getAppStandbyInternal()); // Trigger user interaction. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{restrictBucketThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros, () -> { doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis + windowMs) .when(stats).getStatsEndTimestamp(); mCurrentTimeMillis += windowMs + 1; mIdleStateListener.onUserInteractionStarted(testPkgName, testUser); waitForIdleHandler(mBgRestrictionController.getBackgroundHandler()); // It should have been back to normal. listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_ADAPTIVE_BUCKET); verify(mInjector.getAppStandbyInternal(), atLeast(1)).maybeUnrestrictApp( eq(testPkgName), eq(testUser), eq(REASON_MAIN_USAGE), eq(REASON_SUB_USAGE_USER_INTERACTION), eq(REASON_MAIN_USAGE), eq(REASON_SUB_USAGE_USER_INTERACTION)); }); bgCurrentDrainDecoupleThresholds.set(true); clearInvocations(mInjector.getAppStandbyInternal()); // Go to the threshold right away. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{0, restrictBucketThresholdMah - 1}, new double[]{bgRestrictedThresholdMah + 1, 0}, zeros, zeros, () -> { doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis + windowMs) .when(stats).getStatsEndTimestamp(); mCurrentTimeMillis += windowMs + 1; // We won't change restriction level automatically because it needs // user consent. try { listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); fail("There shouldn't be level change event like this"); } catch (Exception e) { // Expected. } verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket( eq(testPkgName), eq(STANDBY_BUCKET_RARE), eq(testUser), anyInt(), anyInt()); // We should have requested to goto background restricted level. verify(mBgRestrictionController, times(1)).handleRequestBgRestricted( eq(testPkgName), eq(testUid)); // Verify we have the notification posted now because its FGS is invisible. checkNotificationShown(new String[] {testPkgName}, atLeast(1), true); }); bgCurrentDrainDecoupleThresholds.set(false); clearInvocations(mInjector.getAppStandbyInternal()); clearInvocations(mBgRestrictionController); // Go to the threshold right away, but this time, it shouldn't even request to goto // bg restricted level because it requires to be in restricted bucket before that. runTestBgCurrentDrainMonitorOnce(listener, stats, uids, new double[]{0, restrictBucketThresholdMah - 1}, new double[]{bgRestrictedThresholdMah + 1, 0}, zeros, zeros, () -> { doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis + windowMs) .when(stats).getStatsEndTimestamp(); mCurrentTimeMillis += windowMs + 1; // We won't change restriction level automatically because it needs // user consent. try { listener.verify(timeout, testUid, testPkgName, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED); fail("There shouldn't be level change event like this"); } catch (Exception e) { // Expected. } verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket( eq(testPkgName), eq(STANDBY_BUCKET_RARE), eq(testUser), anyInt(), anyInt()); // We should NOT have requested to goto background restricted level. verify(mBgRestrictionController, never()).handleRequestBgRestricted( eq(testPkgName), eq(testUid)); }); } finally { closeIfNotNull(bgCurrentDrainMonitor); closeIfNotNull(bgCurrentDrainWindow); Loading @@ -938,6 +1049,7 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgPromptFgsWithNotiToBgRestricted); closeIfNotNull(bgNotificationMinInterval); closeIfNotNull(bgBatteryExemptionTypes); closeIfNotNull(bgCurrentDrainDecoupleThresholds); } } Loading Loading @@ -1441,6 +1553,7 @@ public final class BackgroundRestrictionTest { DeviceConfigSession<Boolean> bgPermissionMonitorEnabled = null; DeviceConfigSession<String> bgPermissionsInMonitor = null; DeviceConfigSession<Boolean> bgCurrentDrainHighThresholdByBgLocation = null; DeviceConfigSession<Boolean> bgCurrentDrainDecoupleThresholds = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); Loading Loading @@ -1572,6 +1685,13 @@ public final class BackgroundRestrictionTest { R.bool.config_bg_current_drain_high_threshold_by_bg_location)); bgCurrentDrainHighThresholdByBgLocation.set(true); bgCurrentDrainDecoupleThresholds = new DeviceConfigSession<>( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLDS, DeviceConfig::getBoolean, AppBatteryPolicy.DEFAULT_BG_CURRENT_DRAIN_DECOUPLE_THRESHOLD); bgCurrentDrainDecoupleThresholds.set(true); mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); Loading Loading @@ -1990,6 +2110,7 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgPermissionMonitorEnabled); closeIfNotNull(bgPermissionsInMonitor); closeIfNotNull(bgCurrentDrainHighThresholdByBgLocation); closeIfNotNull(bgCurrentDrainDecoupleThresholds); } } Loading