Keep RemoteInputView visible when focused

Scrolls the NotificationStackScrollLayout in response to movement
to ensure that the focused view stays visible. Also makes sure that
the action list is always aligned at the bottom of the notification
to avoid jank during this scrolling when the height changes.

Fixes: 28193862
Fixes: 26919632
Change-Id: I911a873367fe26eafd9fae4bca4e693d0827eba7
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 80eec7e..c66cfba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -243,6 +243,7 @@
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
+            updateForcedScroll();
             updateChildren();
             mChildrenUpdateRequested = false;
             getViewTreeObserver().removeOnPreDrawListener(this);
@@ -334,6 +335,7 @@
     private boolean mDrawBackgroundAsSrc;
     private boolean mFadedOut;
     private boolean mGroupExpandedForMeasure;
+    private View mForcedScroll;
     private float mBackgroundFadeAmount = 1.0f;
     private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
             new FloatProperty<NotificationStackScrollLayout>("backgroundFade") {
@@ -591,6 +593,23 @@
         clampScrollPosition();
     }
 
+    private void updateForcedScroll() {
+        if (mForcedScroll != null && (!mForcedScroll.hasFocus()
+                || !mForcedScroll.isAttachedToWindow())) {
+            mForcedScroll = null;
+        }
+        if (mForcedScroll != null) {
+            ExpandableView expandableView = (ExpandableView) mForcedScroll;
+            int positionInLinearLayout = getPositionInLinearLayout(expandableView);
+            int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
+
+            targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
+            if (mOwnScrollY < targetScroll || positionInLinearLayout < mOwnScrollY) {
+                mOwnScrollY = targetScroll;
+            }
+        }
+    }
+
     private void requestChildrenUpdate() {
         if (!mChildrenUpdateRequested) {
             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
@@ -978,11 +997,19 @@
         mScrollingEnabled = enable;
     }
 
+    @Override
+    public void lockScrollTo(View v) {
+        if (mForcedScroll == v) {
+            return;
+        }
+        mForcedScroll = v;
+        scrollTo(v);
+    }
+
+    @Override
     public boolean scrollTo(View v) {
         ExpandableView expandableView = (ExpandableView) v;
-        int positionInLinearLayout = getPositionInLinearLayout(v);
-        int targetScroll = positionInLinearLayout + expandableView.getIntrinsicHeight() +
-                getImeInset() - getHeight() + getTopPadding();
+        int targetScroll = targetScrollForView(expandableView, getPositionInLinearLayout(v));
 
         if (mOwnScrollY < targetScroll) {
             mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
@@ -993,6 +1020,15 @@
         return false;
     }
 
+    /**
+     * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
+     *         the IME.
+     */
+    private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
+        return positionInLinearLayout + v.getIntrinsicHeight() +
+                getImeInset() - getHeight() + getTopPadding();
+    }
+
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mBottomInset = insets.getSystemWindowInsetBottom();
@@ -1111,6 +1147,7 @@
         if (ev.getY() < mQsContainer.getBottom()) {
             return false;
         }
+        mForcedScroll = null;
         initVelocityTrackerIfNotExists();
         mVelocityTracker.addMovement(ev);
 
@@ -2787,6 +2824,14 @@
     }
 
     @Override
+    public void clearChildFocus(View child) {
+        super.clearChildFocus(child);
+        if (mForcedScroll == child) {
+            mForcedScroll = null;
+        }
+    }
+
+    @Override
     public void requestDisallowLongPress() {
         removeLongPressCallback();
     }