Send deleteIntent when a bubble is dismissed

Test: atest BubbleControllerTest
Bug: 124381186
Change-Id: Ie332dc394c25a143e75b3f9caa46de6386b392db
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 51b2098..eb85589 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -23,6 +23,8 @@
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
@@ -42,6 +44,7 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.MainThread;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -59,6 +62,8 @@
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 
+import java.lang.annotation.Retention;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -70,10 +75,22 @@
  */
 @Singleton
 public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListener {
-    private static final int MAX_BUBBLES = 5; // TODO: actually enforce this
 
     private static final String TAG = "BubbleController";
 
+    private static final int MAX_BUBBLES = 5; // TODO: actually enforce this
+
+    @Retention(SOURCE)
+    @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
+            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION})
+    @interface DismissReason {}
+    static final int DISMISS_USER_GESTURE = 1;
+    static final int DISMISS_AGED = 2;
+    static final int DISMISS_TASK_FINISHED = 3;
+    static final int DISMISS_BLOCKED = 4;
+    static final int DISMISS_NOTIF_CANCEL = 5;
+    static final int DISMISS_ACCESSIBILITY_ACTION = 6;
+
     // Enables some subset of notifs to automatically become bubbles
     private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
 
@@ -248,11 +265,11 @@
     /**
      * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
      */
-    void dismissStack() {
+    void dismissStack(@DismissReason int reason) {
         if (mStackView == null) {
             return;
         }
-        mStackView.stackDismissed();
+        mStackView.stackDismissed(reason);
 
         updateVisibility();
         mNotificationEntryManager.updateNotifications();
@@ -304,9 +321,9 @@
      * Must be called from the main thread.
      */
     @MainThread
-    void removeBubble(String key) {
+    void removeBubble(String key, int reason) {
         if (mStackView != null) {
-            mStackView.removeBubble(key);
+            mStackView.removeBubble(key, reason);
         }
         mNotificationEntryManager.updateNotifications();
         updateVisibility();
@@ -320,7 +337,7 @@
             boolean samePackage = entry.notification.getPackageName().equals(
                     e.notification.getPackageName());
             if (samePackage) {
-                removeBubble(entry.key);
+                removeBubble(entry.key, DISMISS_BLOCKED);
             }
         }
     }
@@ -377,7 +394,7 @@
             }
             if (!removedByUser) {
                 // This was a cancel so we should remove the bubble
-                removeBubble(entry.key);
+                removeBubble(entry.key, DISMISS_NOTIF_CANCEL);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 25ee87a..856b9d6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -135,7 +135,8 @@
         public void onTaskRemovalStarted(int taskId) {
             if (mEntry != null) {
                 // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mEntry.key));
+                post(() -> mBubbleController.removeBubble(mEntry.key,
+                        BubbleController.DISMISS_TASK_FINISHED));
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 97aa786..13fc7fe 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,6 +19,8 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Outline;
@@ -49,6 +51,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController.DismissReason;
 import com.android.systemui.bubbles.animation.ExpandedAnimationController;
 import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
 import com.android.systemui.bubbles.animation.StackAnimationController;
@@ -62,6 +65,7 @@
  */
 public class BubbleStackView extends FrameLayout {
     private static final String TAG = "BubbleStackView";
+    private static final boolean DEBUG = false;
 
     /**
      * Friction applied to fling animations. Since the stack must land on one of the sides of the
@@ -252,7 +256,7 @@
         }
         switch (action) {
             case AccessibilityNodeInfo.ACTION_DISMISS:
-                stackDismissed();
+                stackDismissed(BubbleController.DISMISS_ACCESSIBILITY_ACTION);
                 return true;
             case AccessibilityNodeInfo.ACTION_COLLAPSE:
                 collapseStack();
@@ -376,18 +380,12 @@
     /**
      * Remove a bubble from the stack.
      */
-    public void removeBubble(String key) {
+    public void removeBubble(String key, int reason) {
         Bubble b = mBubbleData.removeBubble(key);
         if (b == null) {
             return;
         }
-        b.entry.setBubbleDismissed(true);
-
-        // Remove it from the views
-        int removedIndex = mBubbleContainer.indexOfChild(b.iconView);
-        b.expandedView.cleanUpExpandedState();
-        mBubbleContainer.removeView(b.iconView);
-
+        int removedIndex = dismissBubble(b, reason);
         int bubbleCount = mBubbleContainer.getChildCount();
         if (bubbleCount == 0) {
             // If no bubbles remain, collapse the entire stack.
@@ -405,26 +403,63 @@
                 mExpandedBubble = null;
             }
         }
+        // TODO: consider logging reason code
         logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
     }
 
     /**
      * Dismiss the stack of bubbles.
      */
-    public void stackDismissed() {
+    public void stackDismissed(int reason) {
         for (Bubble bubble : mBubbleData.getBubbles()) {
-            bubble.entry.setBubbleDismissed(true);
-            bubble.expandedView.cleanUpExpandedState();
+            dismissBubble(bubble, reason);
         }
         mBubbleData.clear();
         collapseStack();
         mBubbleContainer.removeAllViews();
         mExpandedViewContainer.removeAllViews();
+        // TODO: consider logging reason code
         logBubbleEvent(null /* no bubble associated with bubble stack dismiss */,
                 StatsLog.BUBBLE_UICHANGED__ACTION__STACK_DISMISSED);
     }
 
     /**
+     * Marks the notification entry as dismissed, cleans up Bubble icon and expanded view UI
+     * elements and calls deleteIntent if necessary.
+     *
+     * <p>Note: This does not remove the Bubble from BubbleData.
+     *
+     * @param bubble the Bubble being dismissed
+     * @param reason code for the reason the dismiss was triggered
+     * @see BubbleController.DismissReason
+     */
+    private int dismissBubble(Bubble bubble, @DismissReason int reason) {
+        if (DEBUG) {
+            Log.d(TAG, "dismissBubble: " + bubble + " reason=" + reason);
+        }
+        bubble.entry.setBubbleDismissed(true);
+        bubble.expandedView.cleanUpExpandedState();
+
+        // Remove it from the views
+        int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView);
+        mBubbleContainer.removeViewAt(removedIndex);
+
+        if (reason == BubbleController.DISMISS_USER_GESTURE) {
+            Notification.BubbleMetadata bubbleMetadata = bubble.entry.getBubbleMetadata();
+            PendingIntent deleteIntent = bubbleMetadata.getDeleteIntent();
+            if (deleteIntent != null) {
+                try {
+                    deleteIntent.send();
+                } catch (PendingIntent.CanceledException e) {
+                    Log.w(TAG, "Failed to send delete intent for bubble with key: "
+                            + (bubble.entry != null ? bubble.entry.key : " null entry"));
+                }
+            }
+        }
+        return removedIndex;
+    }
+
+    /**
      * Updates a bubble in the stack.
      *
      * @param entry the entry to update in the stack.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index c8eebac..a7170d0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -140,7 +140,7 @@
             case MotionEvent.ACTION_UP:
                 trackMovement(event);
                 if (mInDismissTarget && isStack) {
-                    mController.dismissStack();
+                    mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
                 } else if (mMovedEnough) {
                     mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
                     final float velX = mVelocityTracker.getXVelocity();
@@ -152,7 +152,8 @@
                         mStack.onBubbleDragFinish(
                                 mTouchedView, viewX, viewY, velX, velY, /* dismissed */ dismissed);
                         if (dismissed) {
-                            mController.removeBubble(((BubbleView) mTouchedView).getKey());
+                            mController.removeBubble(((BubbleView) mTouchedView).getKey(),
+                                    BubbleController.DISMISS_USER_GESTURE);
                         }
                     }
                 } else if (mTouchedView == mStack.getExpandedBubbleView()) {