Implement nice transition for unlocking while QS is open

Also allow to swipe from the very bottom on Keyguard to close both
QS and notifications, to have the same behavior like in the normal
shade.

Bug: 18167287
Change-Id: I2150ac5834efe1061ca8c8d32815a481c0796c97
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 074a44e..d9e44c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -527,13 +527,7 @@
                 mIntercepting = false;
                 break;
         }
-
-        // Allow closing the whole panel when in SHADE state.
-        if (mStatusBarState == StatusBarState.SHADE) {
-            return super.onInterceptTouchEvent(event);
-        } else {
-            return !mQsExpanded && super.onInterceptTouchEvent(event);
-        }
+        return super.onInterceptTouchEvent(event);
     }
 
     @Override
@@ -645,10 +639,9 @@
     }
 
     private boolean isInQsArea(float x, float y) {
-        return mStatusBarState != StatusBarState.SHADE ||
-                (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) &&
-                        (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
-                                || y <= mQsContainer.getY() + mQsContainer.getHeight());
+        return (x >= mScrollView.getLeft() && x <= mScrollView.getRight()) &&
+                (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+                || y <= mQsContainer.getY() + mQsContainer.getHeight());
     }
 
     private void handleQsDown(MotionEvent event) {
@@ -1119,9 +1112,26 @@
     }
 
     private float calculateQsTopPadding() {
-        // We can only do the smoother transition on Keyguard when we also are not collapsing from a
-        // scrolled quick settings.
-        if (mKeyguardShowing && mScrollYOverride == -1) {
+        if (mKeyguardShowing
+                && (mTwoFingerQsExpand || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
+
+            // Either QS pushes the notifications down when fully expanded, or QS is fully above the
+            // notifications (mostly on tablets). maxNotifications denotes the normal top padding
+            // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to
+            // take the maximum and linearly interpolate with the panel expansion for a nice motion.
+            int maxNotifications = mClockPositionResult.stackScrollerPadding
+                    - mClockPositionResult.stackScrollerPaddingAdjustment
+                    - mNotificationTopPadding;
+            int maxQs = getTempQsMaxExpansion();
+            int max = mStatusBarState == StatusBarState.KEYGUARD
+                    ? Math.max(maxNotifications, maxQs)
+                    : maxQs;
+            return (int) interpolate(getExpandedFraction(),
+                    mQsMinExpansionHeight, max);
+        } else if (mKeyguardShowing && mScrollYOverride == -1) {
+
+            // We can only do the smoother transition on Keyguard when we also are not collapsing
+            // from a scrolled quick settings.
             return interpolate(getQsExpansionFraction(),
                     mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
                     mQsMaxExpansionHeight);
@@ -1133,7 +1143,9 @@
     private void requestScrollerTopPaddingUpdate(boolean animate) {
         mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
                 mScrollView.getScrollY(),
-                mAnimateNextTopPaddingChange || animate);
+                mAnimateNextTopPaddingChange || animate,
+                mKeyguardShowing
+                        && (mTwoFingerQsExpand || mIsExpanding && mQsExpandedWhenExpandingStarted));
         mAnimateNextTopPaddingChange = false;
     }
 
@@ -1263,18 +1275,27 @@
 
     @Override
     protected void onHeightUpdated(float expandedHeight) {
-        if (!mQsExpanded) {
+        if (!mQsExpanded || mTwoFingerQsExpand || mIsExpanding && mQsExpandedWhenExpandingStarted) {
             positionClockAndNotifications();
         }
         if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
                 && !mQsExpansionFromOverscroll) {
-            float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
-                    + mNotificationStackScroller.getMinStackHeight()
-                    + mNotificationStackScroller.getNotificationTopPadding();
-            float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
-            float t = (expandedHeight - panelHeightQsCollapsed)
-                    / (panelHeightQsExpanded - panelHeightQsCollapsed);
+            float t;
+            if (mKeyguardShowing) {
 
+                // On Keyguard, interpolate the QS expansion linearly to the panel expansion
+                t = expandedHeight / getMaxPanelHeight();
+            } else {
+
+                // In Shade, interpolate linearly such that QS is closed whenever panel height is
+                // minimum QS expansion + minStackHeight
+                float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
+                        + mNotificationStackScroller.getMinStackHeight()
+                        + mNotificationStackScroller.getNotificationTopPadding();
+                float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
+                t = (expandedHeight - panelHeightQsCollapsed)
+                        / (panelHeightQsExpanded - panelHeightQsCollapsed);
+            }
             setQsExpansion(mQsMinExpansionHeight
                     + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
         }
@@ -1308,8 +1329,10 @@
         float notificationHeight = mNotificationStackScroller.getHeight()
                 - mNotificationStackScroller.getEmptyBottomMargin()
                 - mNotificationStackScroller.getTopPadding();
-        float totalHeight = mQsMaxExpansionHeight + notificationHeight
-                + mNotificationStackScroller.getNotificationTopPadding();
+        float totalHeight = Math.max(
+                mQsMaxExpansionHeight + mNotificationStackScroller.getNotificationTopPadding(),
+                mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment)
+                + notificationHeight;
         if (totalHeight > mNotificationStackScroller.getHeight()) {
             float fullyCollapsedHeight = mQsMaxExpansionHeight
                     + mNotificationStackScroller.getMinStackHeight()
@@ -1442,7 +1465,7 @@
         super.onExpandingStarted();
         mNotificationStackScroller.onExpansionStarted();
         mIsExpanding = true;
-        mQsExpandedWhenExpandingStarted = mQsExpanded;
+        mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
         if (mQsExpanded) {
             onQsExpansionStarted();
         }
@@ -1496,11 +1519,12 @@
     @Override
     protected void onTrackingStarted() {
         super.onTrackingStarted();
+        if (mQsFullyExpanded) {
+            mTwoFingerQsExpand = true;
+        }
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
             mAfforanceHelper.animateHideLeftRightIcon();
-        } else if (mQsExpanded) {
-            mTwoFingerQsExpand = true;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index d6c6152..c706ef0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -639,10 +639,10 @@
         }
 
         mExpandedHeight = Math.max(0, mExpandedHeight);
-        onHeightUpdated(mExpandedHeight);
         mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
                 ? 0
                 : mExpandedHeight / fhWithoutOverExpansion);
+        onHeightUpdated(mExpandedHeight);
         notifyBarPanelExpansionChanged();
     }
 
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 aa93d2c..87ce565 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -71,6 +71,12 @@
     private SwipeHelper mSwipeHelper;
     private boolean mSwipingInProgress;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
+
+    /**
+     * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
+     * externally from {@link #setStackHeight}
+     */
+    private float mLastSetStackHeight;
     private int mOwnScrollY;
     private int mMaxLayoutHeight;
 
@@ -453,6 +459,7 @@
      * @param height the new height of the stack
      */
     public void setStackHeight(float height) {
+        mLastSetStackHeight = height;
         setIsExpanded(height > 0.0f);
         int newStackHeight = (int) height;
         int minStackHeight = getMinStackHeight();
@@ -1349,7 +1356,19 @@
                 && initialVelocity > 0;
     }
 
-    public void updateTopPadding(float qsHeight, int scrollY, boolean animate) {
+    /**
+     * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
+     * account.
+     *
+     * @param qsHeight the top padding imposed by the quick settings panel
+     * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
+     *                container
+     * @param animate whether to animate the change
+     * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
+     *                               {@code qsHeight} is the final top padding
+     */
+    public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
+            boolean ignoreIntrinsicPadding) {
         float start = qsHeight - scrollY + mNotificationTopPadding;
         float stackHeight = getHeight() - start;
         int minStackHeight = getMinStackHeight();
@@ -1357,13 +1376,13 @@
             float overflow = minStackHeight - stackHeight;
             stackHeight = minStackHeight;
             start = getHeight() - stackHeight;
-            setTranslationY(overflow);
             mTopPaddingOverflow = overflow;
         } else {
-            setTranslationY(0);
             mTopPaddingOverflow = 0;
         }
-        setTopPadding(clampPadding((int) start), animate);
+        setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
+                animate);
+        setStackHeight(mLastSetStackHeight);
     }
 
     public int getNotificationTopPadding() {