[Roll Forward] Block background custom toasts

Previous CL (ag/9699132) broke CTS ToastTest and was reverted (ag/9754500).
This was because the test was posting a background toast and verifying it
wasn't clickable. Test has been updated to use a foreground toast in ag/9754274
and I will also look into having it be executed in presubmit.(b/144810971).

-- Previous CL description (updated "Test:" lines)

To assess app compatibility problems early on, this CL blocks background
custom toasts in a non-secure way. We create a new method in the AIDL called
enqueueTextToast() that now contain the same parameters as
enqueueToast(), receiving the view created inside the app's process. But this
method will, in the future, contain a CharSequence instead of
ITransientNotification, for window creation inside the system.

We use PlatformCompat infrastructure to check if the change is enabled.

I learned with this change that if user blocks notifications for an app, it
won't be able to show background toasts, allowing me to re-use the foreground
check that was already in place.

This change will display a text toast (from the system) in case the app's
custom toast gets blocked, this is temporary to get feedback from
dogfood.

Test: With sample app, verify that:
      1. Posting text toast in bg works
      2. Posting custom toast in fg works
      3. Posting custom toast in bg does NOT work
Test: atest CtsToastTestCases CtsWindowManagerDeviceTestCases:ToastTest
      CtsWidgetTestCases:ToastTest CtsToastLegacyTestCases
Test: Working on CTS
Bug: 128611929

Change-Id: I25c6339c6adeb907878596332f57e2fa229bfda9
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ac8b604..9aca223 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -44,6 +44,8 @@
     void cancelAllNotifications(String pkg, int userId);
 
     void clearData(String pkg, int uid, boolean fromApp);
+    // TODO: Replace parameter (ITransientNotification callback) with (CharSequence text)
+    void enqueueTextToast(String pkg, ITransientNotification callback, int duration, int displayId);
     @UnsupportedAppUsage
     void enqueueToast(String pkg, ITransientNotification callback, int duration, int displayId);
     @UnsupportedAppUsage
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index d037337..bdc2f9a 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -100,6 +100,8 @@
     @UnsupportedAppUsage
     int mDuration;
     View mNextView;
+    // TODO(b/128611929): Remove this and check for null view when toast creation is in the system
+    boolean mIsCustomToast = false;
 
     /**
      * Construct an empty Toast object.  You must call {@link #setView} before you
@@ -140,7 +142,11 @@
         final int displayId = mContext.getDisplayId();
 
         try {
-            service.enqueueToast(pkg, tn, mDuration, displayId);
+            if (mIsCustomToast) {
+                service.enqueueToast(pkg, tn, mDuration, displayId);
+            } else {
+                service.enqueueTextToast(pkg, tn, mDuration, displayId);
+            }
         } catch (RemoteException e) {
             // Empty
         }
@@ -160,6 +166,7 @@
      * @see #getView
      */
     public void setView(View view) {
+        mIsCustomToast = true;
         mNextView = view;
     }
 
@@ -168,6 +175,7 @@
      * @see #setView
      */
     public View getView() {
+        mIsCustomToast = true;
         return mNextView;
     }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 770de09..3f5bb15 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -117,6 +117,10 @@
         "dnsresolver_aidl_interface-V2-java",
         "netd_event_listener_interface-java",
     ],
+
+    plugins: [
+        "compat-changeid-annotation-processor",
+    ],
 }
 
 java_genrule {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 5e76401..70fbd98 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -131,6 +131,7 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.companion.ICompanionDeviceManager;
+import android.compat.annotation.ChangeId;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -216,6 +217,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -236,6 +238,7 @@
 import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.UiThread;
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
@@ -355,6 +358,15 @@
     private static final int REQUEST_CODE_TIMEOUT = 1;
     private static final String SCHEME_TIMEOUT = "timeout";
     private static final String EXTRA_KEY = "key";
+
+    /**
+     * Apps targeting R+ that post custom toasts in the background will have those blocked. Apps can
+     * still post toasts created with {@link Toast#makeText(Context, CharSequence, int)} and its
+     * variants while in the background.
+     */
+    @ChangeId
+    private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L;
+
     private IActivityManager mAm;
     private ActivityManager mActivityManager;
     private IPackageManager mPackageManager;
@@ -372,9 +384,11 @@
     private UriGrantsManagerInternal mUgmInternal;
     private RoleObserver mRoleObserver;
     private UserManager mUm;
+    private IPlatformCompat mPlatformCompat;
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
+    private Handler mUiHandler;
     private final HandlerThread mRankingThread = new HandlerThread("ranker",
             Process.THREAD_PRIORITY_BACKGROUND);
 
@@ -1773,8 +1787,11 @@
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
         mDpm = dpm;
         mUm = userManager;
+        mPlatformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
 
         mHandler = new WorkerHandler(looper);
+        mUiHandler = new Handler(UiThread.get().getLooper());
         String[] extractorNames;
         try {
             extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -2434,9 +2451,19 @@
         // ============================================================================
 
         @Override
+        public void enqueueTextToast(String pkg, ITransientNotification callback, int duration,
+                int displayId) {
+            enqueueToast(pkg, callback, duration, displayId, false);
+        }
+
+        @Override
         public void enqueueToast(String pkg, ITransientNotification callback, int duration,
-                int displayId)
-        {
+                int displayId) {
+            enqueueToast(pkg, callback, duration, displayId, true);
+        }
+
+        private void enqueueToast(String pkg, ITransientNotification callback, int duration,
+                int displayId, boolean isCustomToast) {
             if (DBG) {
                 Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                         + " duration=" + duration + " displayId=" + displayId);
@@ -2448,27 +2475,54 @@
             }
 
             final int callingUid = Binder.getCallingUid();
+            final UserHandle callingUser = Binder.getCallingUserHandle();
             final boolean isSystemToast = isCallerSystemOrPhone()
                     || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
             final boolean isPackageSuspended = isPackagePaused(pkg);
             final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
                     callingUid);
 
+            final boolean appIsForeground;
             long callingIdentity = Binder.clearCallingIdentity();
             try {
-                final boolean appIsForeground = mActivityManager.getUidImportance(callingUid)
+                appIsForeground = mActivityManager.getUidImportance(callingUid)
                         == IMPORTANCE_FOREGROUND;
-                if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
-                        && !appIsForeground) || isPackageSuspended)) {
-                    Slog.e(TAG, "Suppressing toast from package " + pkg
-                            + (isPackageSuspended ? " due to package suspended."
-                            : " by user request."));
-                    return;
-                }
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
 
+            if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
+                    && !appIsForeground) || isPackageSuspended)) {
+                Slog.e(TAG, "Suppressing toast from package " + pkg
+                        + (isPackageSuspended ? " due to package suspended."
+                        : " by user request."));
+                return;
+            }
+
+            if (isCustomToast && !appIsForeground && !isSystemToast) {
+                boolean block;
+                try {
+                    block = mPlatformCompat.isChangeEnabledByPackageName(
+                            CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, pkg,
+                            callingUser.getIdentifier());
+                } catch (RemoteException e) {
+                    // Shouldn't happen have since it's a local local
+                    Slog.e(TAG, "Unexpected exception while checking block background custom toasts"
+                            + " change", e);
+                    block = false;
+                }
+                if (block) {
+                    // TODO(b/144152069): Remove informative toast
+                    mUiHandler.post(() -> Toast.makeText(getContext(),
+                            "Background custom toast blocked for package " + pkg + ".\n"
+                                    + "See go/r-toast-block.",
+                            Toast.LENGTH_SHORT).show());
+                    Slog.w(TAG, "Blocking custom toast from package " + pkg
+                            + " due to package not in the foreground");
+                    return;
+                }
+            }
+
             synchronized (mToastQueue) {
                 int callingPid = Binder.getCallingPid();
                 long callingId = Binder.clearCallingIdentity();