Make status bar full screen when bubbles are present

* Adds new state 'bubblesShowing' to StatusBarWindowController
* When bubbles are showing status bar will be full screen and
  the touchable region will be updated

Test: manual / existing tests pass (atest SystemUITests)
Bug: 111236845
Change-Id: I6d28d0313104929fba49d326a72209f701eb78d5
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 53862fb..ddb8166 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.bubbles;
 
-import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
@@ -26,6 +26,7 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.service.notification.StatusBarNotification;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -58,6 +59,7 @@
 
     private Context mContext;
     private BubbleDismissListener mDismissListener;
+    private BubbleStateChangeListener mStateChangeListener;
 
     private Map<String, BubbleView> mBubbles = new HashMap<>();
     private BubbleStackView mStackView;
@@ -66,6 +68,9 @@
     // Bubbles get added to the status bar view
     private StatusBarWindowController mStatusBarWindowController;
 
+    // Used for determining view rect for touch interaction
+    private Rect mTempRect = new Rect();
+
     /**
      * Listener to find out about bubble / bubble stack dismissal events.
      */
@@ -81,6 +86,16 @@
         void onBubbleDismissed(String key);
     }
 
+    /**
+     * Listener to be notified when some states of the bubbles change.
+     */
+    public interface BubbleStateChangeListener {
+        /**
+         * Called when the stack has bubbles or no longer has bubbles.
+         */
+        void onHasBubblesChanged(boolean hasBubbles);
+    }
+
     public BubbleController(Context context) {
         mContext = context;
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
@@ -97,6 +112,13 @@
     }
 
     /**
+     * Set a listener to be notified when some states of the bubbles change.
+     */
+    public void setBubbleStateChangeListener(BubbleStateChangeListener listener) {
+        mStateChangeListener = listener;
+    }
+
+    /**
      * Whether or not there are bubbles present, regardless of them being visible on the
      * screen (e.g. if on AOD).
      */
@@ -124,7 +146,9 @@
      * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
      */
     public void dismissStack() {
-        mStackView.setVisibility(GONE);
+        if (mStackView == null) {
+            return;
+        }
         Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
         // Reset the position of the stack (TODO - or should we save / respect last user position?)
         mStackView.setPosition(startPoint.x, startPoint.y);
@@ -134,6 +158,7 @@
         if (mDismissListener != null) {
             mDismissListener.onStackDismissed();
         }
+        updateBubblesShowing();
     }
 
     /**
@@ -150,25 +175,25 @@
             bubble.setNotif(notif);
             mBubbles.put(bubble.getKey(), bubble);
 
-            boolean setPosition = false;
+            boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
             if (mStackView == null) {
                 setPosition = true;
                 mStackView = new BubbleStackView(mContext);
-                ViewGroup sbv = (ViewGroup) mStatusBarWindowController.getStatusBarView();
+                ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
                 // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim
                 // between bubble and the shade
                 int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
                 sbv.addView(mStackView, bubblePosition,
                         new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
             }
-            mStackView.setVisibility(VISIBLE);
             mStackView.addBubble(bubble);
-
             if (setPosition) {
                 // Need to add the bubble to the stack before we can know the width
                 Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
                 mStackView.setPosition(startPoint.x, startPoint.y);
+                mStackView.setVisibility(VISIBLE);
             }
+            updateBubblesShowing();
         }
     }
 
@@ -177,13 +202,32 @@
      */
     public void removeBubble(String key) {
         BubbleView bv = mBubbles.get(key);
-        if (bv != null) {
+        if (mStackView != null && bv != null) {
             mStackView.removeBubble(bv);
             bv.getEntry().setBubbleDismissed(true);
         }
         if (mDismissListener != null) {
             mDismissListener.onBubbleDismissed(key);
         }
+        updateBubblesShowing();
+    }
+
+    private void updateBubblesShowing() {
+        boolean hasBubblesShowing = false;
+        for (BubbleView bv : mBubbles.values()) {
+            if (!bv.getEntry().isBubbleDismissed()) {
+                hasBubblesShowing = true;
+                break;
+            }
+        }
+        boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
+        mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
+        if (mStackView != null && !hasBubblesShowing) {
+            mStackView.setVisibility(INVISIBLE);
+        }
+        if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
+            mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
+        }
     }
 
     /**
@@ -205,14 +249,25 @@
         for (BubbleView view : viewsToRemove) {
             mBubbles.remove(view.getKey());
             mStackView.removeBubble(view);
-            if (mBubbles.size() == 0) {
-                ((ViewGroup) mStatusBarWindowController.getStatusBarView()).removeView(mStackView);
-                mStackView = null;
-            }
         }
         if (mStackView != null) {
-            mStackView.setVisibility(visible ? VISIBLE : GONE);
+            mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
+            if (!visible) {
+                collapseStack();
+            }
         }
+        updateBubblesShowing();
+    }
+
+    /**
+     * Rect indicating the touchable region for the bubble stack / expanded stack.
+     */
+    public Rect getTouchableRegion() {
+        if (mStackView == null || mStackView.getVisibility() != VISIBLE) {
+            return null;
+        }
+        mStackView.getBoundsOnScreen(mTempRect);
+        return mTempRect;
     }
 
     // TODO: factor in PIP location / maybe last place user had it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index d05893d..00d6b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -37,6 +37,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.ScreenDecorations;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarStateController.StateListener;
@@ -56,8 +57,8 @@
  * A implementation of HeadsUpManager for phone and car.
  */
 public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
-       ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
-       OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener {
+        ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
+        OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener {
     private static final String TAG = "HeadsUpManagerPhone";
 
     private final View mStatusBarWindowView;
@@ -78,11 +79,13 @@
     private int[] mTmpTwoArray = new int[2];
     private boolean mHeadsUpGoingAway;
     private boolean mWaitingOnCollapseWhenGoingAway;
+    private boolean mBubbleGoingAway;
     private boolean mIsObserving;
     private int mStatusBarState;
 
     private final StateListener mStateListener = this::setStatusBarState;
     private AnimationStateHandler mAnimationStateHandler;
+    private BubbleController mBubbleController = Dependency.get(BubbleController.class);
 
     private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
         private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
@@ -127,6 +130,12 @@
             }
         });
         Dependency.get(StatusBarStateController.class).addListener(mStateListener);
+        mBubbleController.setBubbleStateChangeListener((hasBubbles) -> {
+            if (!hasBubbles) {
+                mBubbleGoingAway = true;
+            }
+            updateTouchableRegionListener();
+        });
     }
 
     public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -210,6 +219,9 @@
                 mHeadsUpGoingAway = false;
                 updateTouchableRegionListener();
             }
+            if (mBubbleController.hasBubbles() || !mIsExpanded) {
+                updateTouchableRegionListener();
+            }
         }
     }
 
@@ -310,6 +322,11 @@
         } else {
             setCollapsedTouchableInsets(info);
         }
+        Rect r = mBubbleController.getTouchableRegion();
+        if (r != null) {
+            info.touchableRegion.union(r);
+        }
+        mBubbleGoingAway = false;
     }
 
     private void setCollapsedTouchableInsets(ViewTreeObserver.InternalInsetsInfo info) {
@@ -428,8 +445,11 @@
         });
     }
 
+    // TODO: some kind of TouchableRegionManager to deal with this, HeadsUpManager is not really
+    // the right place
     private void updateTouchableRegionListener() {
         boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
+                || mBubbleController.hasBubbles() || mBubbleGoingAway
                 || mWaitingOnCollapseWhenGoingAway
                 || mStatusBarWindowView.getRootWindowInsets().getDisplayCutout() != null;
         if (shouldObserve == mIsObserving) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index cf29cfa..62b6d91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -65,7 +65,7 @@
     private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
     private final DozeParameters mDozeParameters;
-    private View mStatusBarView;
+    private ViewGroup mStatusBarView;
     private WindowManager.LayoutParams mLp;
     private WindowManager.LayoutParams mLpChanged;
     private boolean mHasTopUi;
@@ -109,7 +109,7 @@
      * @param statusBarView The view to add.
      * @param barHeight The height of the status bar in collapsed state.
      */
-    public void add(View statusBarView, int barHeight) {
+    public void add(ViewGroup statusBarView, int barHeight) {
 
         // Now that the status bar window encompasses the sliding panel and its
         // translucent backdrop, the entire thing is made TRANSLUCENT and is
@@ -138,7 +138,7 @@
         onThemeChanged();
     }
 
-    public View getStatusBarView() {
+    public ViewGroup getStatusBarView() {
         return mStatusBarView;
     }
 
@@ -236,7 +236,7 @@
     private boolean isExpanded(State state) {
         return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
-                || state.headsUpShowing
+                || state.headsUpShowing || state.bubblesShowing
                 || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
     }
 
@@ -473,6 +473,21 @@
         apply(mCurrentState);
     }
 
+    /**
+     * Sets whether there are bubbles showing on the screen.
+     */
+    public void setBubblesShowing(boolean bubblesShowing) {
+        mCurrentState.bubblesShowing = bubblesShowing;
+        apply(mCurrentState);
+    }
+
+    /**
+     * The bubbles showing state for the status bar.
+     */
+    public boolean getBubblesShowing() {
+        return mCurrentState.bubblesShowing;
+    }
+
     public void setStateListener(OtherwisedCollapsedListener listener) {
         mListener = listener;
     }
@@ -525,6 +540,7 @@
         boolean backdropShowing;
         boolean wallpaperSupportsAmbientMode;
         boolean notTouchable;
+        boolean bubblesShowing;
 
         /**
          * The {@link StatusBar} state from the status bar.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index de26c70..98d0c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -28,7 +28,7 @@
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 
 import com.android.systemui.SysuiTestCase;
@@ -37,7 +37,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -51,7 +50,7 @@
     @Mock
     private DozeParameters mDozeParameters;
     @Mock
-    private View mStatusBarView;
+    private ViewGroup mStatusBarView;
     @Mock
     private IActivityManager mActivityManager;