Switch to controling if toast rate limiting is enabled using TestApi.
We tried to use compat changeid to turn rate limiting on/off in CTS
tests but it doesn't work on user builds (see bug). We're therefore
migrating to using a new TestApi guarded by a new permission that we
give to the shell.
Test: atest android.widget.cts.ToastTest
Bug: 175720818
Change-Id: I2ef147ed6449333fc6b4273eca82bce533cb13f3
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4ef24ff..a4d3aa7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -18,6 +18,7 @@
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+ field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
field public static final String MODIFY_REFRESH_RATE_SWITCHING_TYPE = "android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE";
field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
@@ -275,6 +276,7 @@
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
method public boolean matchesCallFilter(android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 697a377..bda2fa9 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -228,4 +228,6 @@
NotificationListenerFilter getListenerFilter(in ComponentName cn, int userId);
void setListenerFilter(in ComponentName cn, int userId, in NotificationListenerFilter nlf);
+
+ void setToastRateLimitingEnabled(boolean enable);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 58f382d..6cce270 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1730,6 +1730,23 @@
}
/**
+ * Controls whether toast rate limiting is enabled for the calling uid.
+ *
+ * @param enable true to enable toast rate limiting, false to disable it
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING)
+ public void setToastRateLimitingEnabled(boolean enable) {
+ INotificationManager service = getService();
+ try {
+ service.setToastRateLimitingEnabled(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notification policy configuration. Represents user-preferences for notification
* filtering.
*/
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8682fea..3053518 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5317,6 +5317,12 @@
<permission android:name="android.permission.BIND_IMPRESSION_ATTESTATION_SERVICE"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f83f670..e572aa4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -378,6 +378,9 @@
<uses-permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" />
<uses-permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS" />
+ <!-- Permission required for CTS tests to enable/disable rate limiting toasts. -->
+ <uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c106558..3751c9b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -445,16 +445,6 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
private static final long NOTIFICATION_TRAMPOLINE_BLOCK = 167676448L;
- /**
- * Rate limit showing toasts, on a per package basis.
- *
- * It limits the effects of {@link android.widget.Toast#show()} calls to prevent overburdening
- * the user with too many toasts in a limited time. Any attempt to show more toasts than allowed
- * in a certain time frame will result in the toast being discarded.
- */
- @ChangeId
- private static final long RATE_LIMIT_TOASTS = 154198299L;
-
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -526,6 +516,9 @@
@GuardedBy("mNotificationLock")
final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
final ArrayList<ToastRecord> mToastQueue = new ArrayList<>();
+ // set of uids for which toast rate limiting is disabled
+ @GuardedBy("mToastQueue")
+ private final Set<Integer> mToastRateLimitingDisabledUids = new ArraySet<>();
final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
// True if the toast that's on top of the queue is being shown at the moment.
@@ -3067,6 +3060,22 @@
}
@Override
+ public void setToastRateLimitingEnabled(boolean enable) {
+ getContext().enforceCallingPermission(
+ android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING,
+ "App doesn't have the permission to enable/disable toast rate limiting");
+
+ synchronized (mToastQueue) {
+ int uid = Binder.getCallingUid();
+ if (enable) {
+ mToastRateLimitingDisabledUids.remove(uid);
+ } else {
+ mToastRateLimitingDisabledUids.add(uid);
+ }
+ }
+ }
+
+ @Override
public void finishToken(String pkg, IBinder token) {
synchronized (mToastQueue) {
final long callingId = Binder.clearCallingIdentity();
@@ -7377,7 +7386,7 @@
while (record != null) {
int userId = UserHandle.getUserId(record.uid);
boolean rateLimitingEnabled =
- CompatChanges.isChangeEnabled(RATE_LIMIT_TOASTS, record.uid);
+ !mToastRateLimitingDisabledUids.contains(record.uid);
boolean isWithinQuota =
mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG);