Handle process die event of a bubble activity gracefully.

When the process behind a bubble activity died (e.g. killed by lmkd,
or app crash), collapse the bubble and restart the activity when it's
expanded next time.

Bug: 126945401
Test: atest TaskStackChangedListenerTest
Change-Id: Ia2ee21f9ced51fe33756b1e12159f445d6ed083d
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 573a066..5d61179 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -762,6 +762,20 @@
                 expandedBubble.setContentVisibility(true);
             }
         }
+
+        @Override
+        public void onSingleTaskDisplayEmpty(int displayId) {
+            final Bubble expandedBubble = getExpandedBubble(mContext);
+            if (expandedBubble == null) {
+                return;
+            }
+            if (expandedBubble.getDisplayId() == displayId) {
+                mBubbleData.setExpanded(false);
+            }
+            if (expandedBubble.expandedView != null) {
+                expandedBubble.expandedView.notifyDisplayEmpty();
+            }
+        }
     }
 
     private static boolean areBubblesEnabled(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 0a19ac3..8c09913 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -63,6 +63,17 @@
 public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
 
+    private enum ActivityViewStatus {
+        // ActivityView is being initialized, cannot start an activity yet.
+        INITIALIZING,
+        // ActivityView is initialized, and ready to start an activity.
+        INITIALIZED,
+        // Activity runs in the ActivityView.
+        ACTIVITY_STARTED,
+        // ActivityView is released, so activity launching will no longer be permitted.
+        RELEASED,
+    }
+
     // The triangle pointing to the expanded view
     private View mPointerView;
     private int mPointerMargin;
@@ -72,7 +83,7 @@
     // Views for expanded state
     private ActivityView mActivityView;
 
-    private boolean mActivityViewReady = false;
+    private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
     private int mTaskId = -1;
 
     private PendingIntent mBubbleIntent;
@@ -98,19 +109,21 @@
     private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
         @Override
         public void onActivityViewReady(ActivityView view) {
-            if (!mActivityViewReady) {
-                mActivityViewReady = true;
-                // Custom options so there is no activity transition animation
-                ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
-                        0 /* enterResId */, 0 /* exitResId */);
-                // Post to keep the lifecycle normal
-                post(() -> mActivityView.startActivity(mBubbleIntent, options));
+            switch (mActivityViewStatus) {
+                case INITIALIZING:
+                case INITIALIZED:
+                    // Custom options so there is no activity transition animation
+                    ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+                            0 /* enterResId */, 0 /* exitResId */);
+                    // Post to keep the lifecycle normal
+                    post(() -> mActivityView.startActivity(mBubbleIntent, options));
+                    mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
             }
         }
 
         @Override
         public void onActivityViewDestroyed(ActivityView view) {
-            mActivityViewReady = false;
+            mActivityViewStatus = ActivityViewStatus.RELEASED;
         }
 
         @Override
@@ -433,8 +446,10 @@
         if (mActivityView == null) {
             return;
         }
-        if (mActivityViewReady) {
-            mActivityView.release();
+        switch (mActivityViewStatus) {
+            case INITIALIZED:
+            case ACTIVITY_STARTED:
+                mActivityView.release();
         }
         if (mTaskId != -1) {
             try {
@@ -447,7 +462,16 @@
         removeView(mActivityView);
 
         mActivityView = null;
-        mActivityViewReady = false;
+    }
+
+    /**
+     * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
+     * which {@link ActivityView} uses.
+     */
+    void notifyDisplayEmpty() {
+        if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
+            mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+        }
     }
 
     private boolean usingActivityView() {