Allow delegates to cancel notifs they posted

They still cannot see or cancel notifications that the delegator
app posted

Test: cts
Bug: 134585713
Change-Id: If2065010ea5f65cdc0b531441be9a9989214afb7
diff --git a/api/current.txt b/api/current.txt
index 0eb0423..261475a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5808,8 +5808,9 @@
     method public boolean areNotificationsPaused();
     method public boolean canNotifyAsPackage(@NonNull String);
     method public void cancel(int);
-    method public void cancel(String, int);
+    method public void cancel(@Nullable String, int);
     method public void cancelAll();
+    method public void cancelAsPackage(@NonNull String, @Nullable String, int);
     method public void createNotificationChannel(@NonNull android.app.NotificationChannel);
     method public void createNotificationChannelGroup(@NonNull android.app.NotificationChannelGroup);
     method public void createNotificationChannelGroups(@NonNull java.util.List<android.app.NotificationChannelGroup>);
@@ -5831,7 +5832,7 @@
     method public boolean isNotificationPolicyAccessGranted();
     method public void notify(int, android.app.Notification);
     method public void notify(String, int, android.app.Notification);
-    method public void notifyAsPackage(@NonNull String, @NonNull String, int, @NonNull android.app.Notification);
+    method public void notifyAsPackage(@NonNull String, @Nullable String, int, @NonNull android.app.Notification);
     method public boolean removeAutomaticZenRule(String);
     method public void setAutomaticZenRuleState(@NonNull String, @NonNull android.service.notification.Condition);
     method public final void setInterruptionFilter(int);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e57738f..4db3725 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -53,7 +53,7 @@
     void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
             in Notification notification, int userId);
     @UnsupportedAppUsage
-    void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
+    void cancelNotificationWithTag(String pkg, String opPkg, String tag, int id, int userId);
 
     void setShowBadge(String pkg, int uid, boolean showBadge);
     boolean canShowBadge(String pkg, int uid);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index dd39376..2e80375 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -466,7 +466,7 @@
      * @param notification A {@link Notification} object describing what to
      *        show the user. Must not be null.
      */
-    public void notifyAsPackage(@NonNull String targetPackage, @NonNull String tag, int id,
+    public void notifyAsPackage(@NonNull String targetPackage, @Nullable String tag, int id,
             @NonNull Notification notification) {
         INotificationManager service = getService();
         String sender = mContext.getPackageName();
@@ -532,9 +532,13 @@
     }
 
     /**
-     * Cancel a previously shown notification.  If it's transient, the view
-     * will be hidden.  If it's persistent, it will be removed from the status
-     * bar.
+     * Cancels a previously posted notification.
+     *
+     *  <p>If the notification does not currently represent a
+     *  {@link Service#startForeground(int, Notification) foreground service}, it will be
+     *  removed from the UI and live
+     *  {@link android.service.notification.NotificationListenerService notification listeners}
+     *  will be informed so they can remove the notification from their UIs.</p>
      */
     public void cancel(int id)
     {
@@ -542,16 +546,49 @@
     }
 
     /**
-     * Cancel a previously shown notification.  If it's transient, the view
-     * will be hidden.  If it's persistent, it will be removed from the status
-     * bar.
+     * Cancels a previously posted notification.
+     *
+     *  <p>If the notification does not currently represent a
+     *  {@link Service#startForeground(int, Notification) foreground service}, it will be
+     *  removed from the UI and live
+     *  {@link android.service.notification.NotificationListenerService notification listeners}
+     *  will be informed so they can remove the notification from their UIs.</p>
      */
-    public void cancel(String tag, int id)
+    public void cancel(@Nullable String tag, int id)
     {
         cancelAsUser(tag, id, mContext.getUser());
     }
 
     /**
+     * Cancels a previously posted notification.
+     *
+     * <p>If the notification does not currently represent a
+     * {@link Service#startForeground(int, Notification) foreground service}, it will be
+     * removed from the UI and live
+     * {@link android.service.notification.NotificationListenerService notification listeners}
+     * will be informed so they can remove the notification from their UIs.</p>
+     *
+     * <p>This method may be used by {@link #getNotificationDelegate() a notification delegate} to
+     * cancel notifications that they have posted via {@link #notifyAsPackage(String, String, int,
+     * Notification)}.</p>
+     *
+     * @param targetPackage The package to cancel the notification as. If this package is not your
+     *                      package, you can only cancel notifications you posted with
+     *                      {@link #notifyAsPackage(String, String, int, Notification).
+     * @param tag A string identifier for this notification.  May be {@code null}.
+     * @param id An identifier for this notification.
+     */
+    public void cancelAsPackage(@NonNull String targetPackage, @Nullable String tag, int id) {
+        INotificationManager service = getService();
+        try {
+            service.cancelNotificationWithTag(targetPackage, mContext.getOpPackageName(),
+                    tag, id, mContext.getUser().getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
@@ -561,7 +598,8 @@
         String pkg = mContext.getPackageName();
         if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
         try {
-            service.cancelNotificationWithTag(pkg, tag, id, user.getIdentifier());
+            service.cancelNotificationWithTag(
+                    pkg, mContext.getOpPackageName(), tag, id, user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1432f57..d848796 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5310,7 +5310,8 @@
         long identityToken = clearCallingIdentity();
         try {
             INotificationManager service = mInjector.getNotificationManager();
-            service.cancelNotificationWithTag(packageName, id.mTag, id.mId, user.getIdentifier());
+            service.cancelNotificationWithTag(
+                    packageName, "android", id.mTag, id.mId, user.getIdentifier());
         } catch (RemoteException e) {
             /* ignore - local call */
         } finally {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index dee8e3b..3f723fc 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -918,7 +918,7 @@
                     return;
                 }
                 try {
-                    inm.cancelNotificationWithTag(localPackageName, null,
+                    inm.cancelNotificationWithTag(localPackageName, "android", null,
                             localForegroundId, userId);
                 } catch (RuntimeException e) {
                     Slog.w(TAG, "Error canceling notification for service", e);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9a0932f..089b1d2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2378,10 +2378,29 @@
         }
 
         @Override
-        public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
-            checkCallerIsSystemOrSameApp(pkg);
+        public void cancelNotificationWithTag(String pkg, String opPkg, String tag, int id,
+                int userId) {
             userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
+
+            // ensure opPkg is delegate if does not match pkg
+            resolveNotificationUid(opPkg, pkg, Binder.getCallingUid(), userId);
+
+            // if opPkg is not the same as pkg, make sure the notification given was posted
+            // by opPkg
+            if (!Objects.equals(pkg, opPkg)) {
+                synchronized (mNotificationLock) {
+                    // Look for the notification, searching both the posted and enqueued lists.
+                    NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
+                    if (r != null) {
+                        if (!Objects.equals(opPkg, r.sbn.getOpPkg())) {
+                            throw new SecurityException(opPkg + " does not have permission to "
+                                    + "cancel a notification they did not post " + tag + " " + id);
+                        }
+                    }
+                }
+            }
+
             // Don't allow client applications to cancel foreground service notis or autobundled
             // summaries.
             final int mustNotHaveFlags = isCallingUidSystem() ? 0 :
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9fc278e..6fe7881 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5683,7 +5683,7 @@
             return;
         }
         try {
-            inm.cancelNotificationWithTag("android", null,
+            inm.cancelNotificationWithTag("android", "android", null,
                     SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION, userId);
         } catch (RuntimeException e) {
             Slog.w(TAG, "Error canceling notification for service", e);
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 7a96f4c..0f11566 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1065,7 +1065,7 @@
 
         waitForLatch(latch);
         // Verify notification is cancelled
-        verify(mMockNotificationManager).cancelNotificationWithTag(
+        verify(mMockNotificationManager).cancelNotificationWithTag(anyString(),
                 anyString(), nullable(String.class), anyInt(), anyInt());
 
         verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
@@ -1943,7 +1943,7 @@
                 UserHandle.USER_SYSTEM);
         waitForLatch(latch);
         // Verify notification is cancelled
-        verify(mMockNotificationManager).cancelNotificationWithTag(
+        verify(mMockNotificationManager).cancelNotificationWithTag(anyString(),
                 anyString(), nullable(String.class), anyInt(), anyInt());
 
         verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index b4b1194..677705d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -733,7 +733,7 @@
                 mService.getNotificationRecord(sbn.getKey()).getImportance());
         assertEquals(IMPORTANCE_LOW, mBinderService.getNotificationChannel(
                 PKG, mContext.getUserId(), PKG, channel.getId()).getImportance());
-        mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
         waitForIdle();
 
         update = new NotificationChannel("blockedbyuser", "name", IMPORTANCE_NONE);
@@ -908,7 +908,7 @@
     public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null).getNotification(), 0);
-        mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
@@ -923,7 +923,7 @@
         waitForIdle();
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
                 generateNotificationRecord(null).getNotification(), 0);
-        mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
@@ -1207,7 +1207,7 @@
         sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
-        mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
         assertEquals(0, mService.getNotificationRecordCount());
@@ -4403,7 +4403,7 @@
                 nr1.sbn.getKey()).getNotification().isBubbleNotification());
 
         // Remove the bubble
-        mBinderService.cancelNotificationWithTag(PKG, "tag", nr1.sbn.getId(),
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", nr1.sbn.getId(),
                 nr1.sbn.getUserId());
         waitForIdle();
 
@@ -4802,7 +4802,7 @@
         assertEquals(1, notifs.length);
         assertEquals(1, mService.getNotificationRecordCount());
 
-        mBinderService.cancelNotificationWithTag(PKG, null, nrBubble.sbn.getId(),
+        mBinderService.cancelNotificationWithTag(PKG, PKG, null, nrBubble.sbn.getId(),
                 nrBubble.sbn.getUserId());
         waitForIdle();