diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 89d90a3b9d6f2..313a34067cd07 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -685,6 +685,14 @@ interface IActivityManager { */ boolean enableAppFreezer(in boolean enable); + /** + * Suppress or reenable the rate limit on foreground service notification deferral. + * This is for use within CTS and is protected by android.permission.WRITE_DEVICE_CONFIG. + * + * @param enable false to suppress rate-limit policy; true to reenable it. + */ + boolean enableFgsNotificationRateLimit(in boolean enable); + /** * Holds the AM lock for the specified amount of milliseconds. * This is intended for use by the tests that need to imitate lock contention. diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 6dd4f040f7651..566f59c4b4de5 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -249,6 +249,14 @@ public final class ActiveServices { */ final ArrayList mPendingFgsNotifications = new ArrayList<>(); + /** + * Whether there is a rate limit that suppresses immediate re-deferral of new FGS + * notifications from each app. On by default, disabled only by shell command for + * test-suite purposes. To disable the behavior more generally, use the usual + * DeviceConfig mechanism to set the rate limit interval to zero. + */ + private boolean mFgsDeferralRateLimited = true; + /** * Uptime at which a given uid becomes eliglible again for FGS notification deferral */ @@ -2142,8 +2150,10 @@ public final class ActiveServices { } } - final long nextEligible = when + mAm.mConstants.mFgsNotificationDeferralExclusionTime; - mFgsDeferralEligible.put(uid, nextEligible); + if (mFgsDeferralRateLimited) { + final long nextEligible = when + mAm.mConstants.mFgsNotificationDeferralExclusionTime; + mFgsDeferralEligible.put(uid, nextEligible); + } r.fgDisplayTime = when; r.mFgsNotificationDeferred = true; r.mFgsNotificationShown = false; @@ -2204,6 +2214,38 @@ public final class ActiveServices { } }; + /** + * Suppress or reenable the rate limit on foreground service notification deferral. + * Invoked from the activity manager shell command. + * + * @param enable false to suppress rate-limit policy; true to reenable it. + */ + boolean enableFgsNotificationRateLimitLocked(final boolean enable) { + if (enable != mFgsDeferralRateLimited) { + mFgsDeferralRateLimited = enable; + if (!enable) { + // make sure to reset any active rate limiting + mFgsDeferralEligible.clear(); + } + } + return enable; + } + + private void removeServiceNotificationDeferralsLocked(String packageName, + final @UserIdInt int userId) { + for (int i = mPendingFgsNotifications.size() - 1; i >= 0; i--) { + final ServiceRecord r = mPendingFgsNotifications.get(i); + if (userId == r.userId + && r.appInfo.packageName.equals(packageName)) { + mPendingFgsNotifications.remove(i); + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "Removing notification deferral for " + + r); + } + } + } + } + private void maybeLogFGSStateEnteredLocked(ServiceRecord r) { if (r.mLogEntering) { logFGSStateChangeLocked(r, @@ -4690,6 +4732,7 @@ public final class ActiveServices { } } removeServiceRestartBackoffEnabledLocked(packageName); + removeServiceNotificationDeferralsLocked(packageName, userId); } void cleanUpServices(int userId, ComponentName component, Intent baseIntent) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6b0e2db3f218e..86b528781699a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17065,6 +17065,19 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Suppress or reenable the rate limit on foreground service notification deferral. + * @param enable false to suppress rate-limit policy; true to reenable it. + */ + @Override + public boolean enableFgsNotificationRateLimit(boolean enable) { + enforceCallingPermission(permission.WRITE_DEVICE_CONFIG, + "enableFgsNotificationRateLimit"); + synchronized (this) { + return mServices.enableFgsNotificationRateLimitLocked(enable); + } + } + /** * Holds the AM lock for the specified amount of milliseconds. * Intended for use by the tests that need to imitate lock contention. diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index f2762fc965eed..0757e7b6bd7d4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -230,6 +230,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runBugReport(pw); case "force-stop": return runForceStop(pw); + case "fgs-notification-rate-limit": + return runFgsNotificationRateLimit(pw); case "crash": return runCrash(pw); case "kill": @@ -1103,6 +1105,24 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runFgsNotificationRateLimit(PrintWriter pw) throws RemoteException { + final String toggleValue = getNextArgRequired(); + final boolean enable; + switch (toggleValue) { + case "enable": + enable = true; + break; + case "disable": + enable = false; + break; + default: + throw new IllegalArgumentException( + "Argument must be either 'enable' or 'disable'"); + } + mInterface.enableFgsNotificationRateLimit(enable); + return 0; + } + int runCrash(PrintWriter pw) throws RemoteException { int userId = UserHandle.USER_ALL; @@ -3303,6 +3323,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" when done to select where it should be delivered. Options are:"); pw.println(" --progress: will launch a notification right away to show its progress."); pw.println(" --telephony: will dump only telephony sections."); + pw.println(" fgs-notification-rate-limit {enable | disable}"); + pw.println(" Enable/disable rate limit on FGS notification deferral policy."); pw.println(" force-stop [--user | all | current] "); pw.println(" Completely stop the given application package."); pw.println(" crash [--user ] ");