Merge "Translate bubbles with IME"
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index e8b346e..49beae6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -94,6 +94,9 @@
     private boolean mActivityViewReady = false;
     private PendingIntent mBubbleIntent;
 
+    private boolean mKeyboardVisible;
+    private boolean mNeedsNewHeight;
+
     private int mMinHeight;
     private int mHeaderHeight;
     private int mBubbleHeight;
@@ -227,21 +230,15 @@
                 true /* singleTaskInstance */);
         addView(mActivityView);
 
-        mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
-            ActivityView activityView = (ActivityView) view;
-            // Here we assume that the position of the ActivityView on the screen
-            // remains regardless of IME status. When we move ActivityView, the
-            // forwardedInsets should be computed not against the current location
-            // and size, but against the post-moved location and size.
-            Point displaySize = new Point();
-            view.getContext().getDisplay().getSize(displaySize);
-            int[] windowLocation = view.getLocationOnScreen();
-            final int windowBottom = windowLocation[1] + view.getHeight();
+        setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
+            // Keep track of IME displaying because we should not make any adjustments that might
+            // cause a config change while the IME is displayed otherwise it'll loose focus.
             final int keyboardHeight = insets.getSystemWindowInsetBottom()
                     - insets.getStableInsetBottom();
-            final int insetsBottom = Math.max(0,
-                    windowBottom + keyboardHeight - displaySize.y);
-            activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
+            mKeyboardVisible = keyboardHeight != 0;
+            if (!mKeyboardVisible && mNeedsNewHeight) {
+                updateHeight();
+            }
             return view.onApplyWindowInsets(insets);
         });
 
@@ -258,6 +255,34 @@
         }
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mKeyboardVisible = false;
+        mNeedsNewHeight = false;
+        if (mActivityView != null) {
+            mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
+        }
+    }
+
+    /**
+     * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
+     * This should be done post-move and post-animation.
+     */
+    void updateInsets(WindowInsets insets) {
+        if (usingActivityView()) {
+            Point displaySize = new Point();
+            mActivityView.getContext().getDisplay().getSize(displaySize);
+            int[] windowLocation = mActivityView.getLocationOnScreen();
+            final int windowBottom = windowLocation[1] + mActivityView.getHeight();
+            final int keyboardHeight = insets.getSystemWindowInsetBottom()
+                    - insets.getStableInsetBottom();
+            final int insetsBottom = Math.max(0,
+                    windowBottom + keyboardHeight - displaySize.y);
+            mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
+        }
+    }
+
     /**
      * Creates a background with corners rounded based on how the view is configured to display
      */
@@ -448,9 +473,15 @@
             int height = Math.min(desiredHeight, max);
             height = Math.max(height, mMinHeight);
             LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
-            lp.height = height;
-            mBubbleHeight = height;
-            mActivityView.setLayoutParams(lp);
+            mNeedsNewHeight =  lp.height != height;
+            if (!mKeyboardVisible) {
+                // If the keyboard is visible... don't adjust the height because that will cause
+                // a configuration change and the keyboard will be lost.
+                lp.height = height;
+                mBubbleHeight = height;
+                mActivityView.setLayoutParams(lp);
+                mNeedsNewHeight = false;
+            }
         } else {
             mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 461e79c..580acb8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -128,6 +128,7 @@
 
     private Bubble mExpandedBubble;
     private boolean mIsExpanded;
+    private boolean mImeVisible;
 
     private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
@@ -236,6 +237,28 @@
         setClipChildren(false);
         setFocusable(true);
         mBubbleContainer.bringToFront();
+
+        setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
+            final int keyboardHeight = insets.getSystemWindowInsetBottom()
+                    - insets.getStableInsetBottom();
+            if (!mIsExpanded) {
+                return view.onApplyWindowInsets(insets);
+            }
+            mImeVisible = keyboardHeight != 0;
+
+            float newY = getYPositionForExpandedView();
+            if (newY < 0) {
+                // TODO: This means our expanded content is too big to fit on screen. Right now
+                // we'll let it translate off but we should be clipping it & pushing the header
+                // down so that it always remains visible.
+            }
+            mExpandedViewYAnim.animateToFinalPosition(newY);
+            mExpandedAnimationController.updateYPosition(
+                    // Update the insets after we're done translating otherwise position
+                    // calculation for them won't be correct.
+                    () -> mExpandedBubble.expandedView.updateInsets(insets));
+            return view.onApplyWindowInsets(insets);
+        });
     }
 
     /**
@@ -646,15 +669,6 @@
         }
     }
 
-    /**
-     * The width of the collapsed stack of bubbles.
-     */
-    public int getStackWidth() {
-        return mBubblePadding * (mBubbleContainer.getChildCount() - 1)
-                + mBubbleSize + mBubbleContainer.getPaddingEnd()
-                + mBubbleContainer.getPaddingStart();
-    }
-
     private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) {
         if (mExpandListener != null) {
             mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
@@ -843,8 +857,13 @@
             // calculation is correct)
             mExpandedBubble.expandedView.updateView();
             final float y = getYPositionForExpandedView();
-            mExpandedViewContainer.setTranslationY(y);
-            // Then update the view so that ActivityView knows we translated
+            if (!mExpandedViewYAnim.isRunning()) {
+                // We're not animating so set the value
+                mExpandedViewContainer.setTranslationY(y);
+            } else {
+                // We are animating so update the value
+                mExpandedViewYAnim.animateToFinalPosition(y);
+            }
             mExpandedBubble.expandedView.updateView();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 1f29883..40e08be 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -198,6 +198,19 @@
     }
 
     /**
+     * Animates the bubbles to {@link #getExpandedY()} position. Used in response to IME showing.
+     */
+    public void updateYPosition(Runnable after) {
+        if (mLayout == null) return;
+
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            boolean isLast = i == mLayout.getChildCount() - 1;
+            mLayout.animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, i,
+                    getExpandedY(), isLast ? after : null);
+        }
+    }
+
+    /**
      * Animates the bubbles, starting at the given index, to the left or right by the given number
      * of bubble widths. Passing zero for numBubbleWidths will animate the bubbles to their normal
      * positions.
@@ -213,18 +226,25 @@
 
     /** The Y value of the row of expanded bubbles. */
     public float getExpandedY() {
-        boolean showOnTop = mLayout != null
-                && BubbleController.showBubblesAtTop(mLayout.getContext());
-        final WindowInsets insets = mLayout != null ? mLayout.getRootWindowInsets() : null;
-        if (showOnTop && insets != null) {
+        if (mLayout == null || mLayout.getRootWindowInsets() == null) {
+            return 0;
+        }
+        final boolean showOnTop = BubbleController.showBubblesAtTop(mLayout.getContext());
+        final WindowInsets insets = mLayout.getRootWindowInsets();
+        if (showOnTop) {
             return mBubblePaddingPx + Math.max(
                     mStatusBarHeight,
                     insets.getDisplayCutout() != null
                             ? insets.getDisplayCutout().getSafeInsetTop()
                             : 0);
         } else {
-            int bottomInset = insets != null ? insets.getSystemWindowInsetBottom() : 0;
-            return mDisplaySize.y - mBubbleSizePx - (mPipDismissHeight - bottomInset);
+            int keyboardHeight = insets.getSystemWindowInsetBottom()
+                    - insets.getStableInsetBottom();
+            float bottomInset = keyboardHeight > 0
+                    ? keyboardHeight
+                    : (mPipDismissHeight - insets.getStableInsetBottom());
+            // Stable insets are excluded from display size, so we must subtract it
+            return mDisplaySize.y - mBubbleSizePx - mBubblePaddingPx - bottomInset;
         }
     }