Improve motion when expanding/collapsing status bar.

- Don't fade the whole panel anymore.
- Parallax effect for QS header translation, fade on keyguard.
- Improve fling curve for dismissing the panel.
- Improve peeking behavior.

Bug: 14804452
Bug: 15407838
Change-Id: I34b7bcd457cb8a037e0bb06e9802ec66d2b39b73
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 30d6e9f..19cfd2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -55,6 +55,7 @@
     private static final int CAP_HEIGHT = 1456;
     private static final int FONT_HEIGHT = 2163;
 
+    private static final float HEADER_RUBBERBAND_FACTOR = 2.15f;
     private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
 
     private KeyguardPageSwipeHelper mPageSwiper;
@@ -97,7 +98,6 @@
     private ValueAnimator mQsExpansionAnimator;
     private FlingAnimationUtils mFlingAnimationUtils;
     private int mStatusBarMinHeight;
-    private boolean mHeaderHidden;
     private boolean mUnlockIconActive;
     private int mNotificationsHeaderCollideDistance;
     private int mUnlockMoveDistance;
@@ -207,9 +207,7 @@
         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
         int stackScrollerPadding;
         if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
-            int bottom = mStackScrollerOverscrolling
-                    ? mHeader.getCollapsedHeight()
-                    : mHeader.getBottom();
+            int bottom = mHeader.getCollapsedHeight();
             stackScrollerPadding = bottom + mQsPeekHeight
                     + mNotificationTopPadding;
             mTopPaddingAdjustment = 0;
@@ -562,6 +560,10 @@
     }
 
     public void setKeyguardShowing(boolean keyguardShowing) {
+        if (!mKeyguardShowing && keyguardShowing) {
+            setQsTranslation(mQsExpansionHeight);
+            mHeader.setTranslationY(0f);
+        }
         mKeyguardShowing = keyguardShowing;
         updateQsState();
     }
@@ -602,10 +604,9 @@
     }
 
     private void setQsTranslation(float height) {
-        mQsContainer.setY(height - mQsContainer.getHeight());
+        mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
     }
 
-
     private void requestScrollerTopPaddingUpdate(boolean animate) {
         mNotificationStackScroller.updateTopPadding(mQsExpansionHeight,
                 mScrollView.getScrollY(),
@@ -728,6 +729,11 @@
 
     @Override
     protected int getMaxPanelHeight() {
+        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
+                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
+            return (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
+                    * HEADER_RUBBERBAND_FACTOR);
+        }
         // TODO: Figure out transition for collapsing when QS is open, adjust height here.
         int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
         int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -746,8 +752,22 @@
             positionClockAndNotifications();
         }
         mNotificationStackScroller.setStackHeight(expandedHeight);
-        updateKeyguardHeaderVisibility();
+        updateHeader();
         updateUnlockIcon();
+        updateNotificationTranslucency();
+    }
+
+    private void updateNotificationTranslucency() {
+        float alpha = (mNotificationStackScroller.getNotificationsTopY()
+                + mNotificationStackScroller.getItemHeight())
+                / (mQsMinExpansionHeight
+                        + mNotificationStackScroller.getItemHeight() / 2);
+        alpha = Math.max(0, Math.min(alpha, 1));
+        alpha = (float) Math.pow(alpha, 0.75);
+
+        // TODO: Draw a rect with DST_OUT over the notifications to achieve the same effect -
+        // this would be much more efficient.
+        mNotificationStackScroller.setAlpha(alpha);
     }
 
     @Override
@@ -786,51 +806,61 @@
     /**
      * Hides the header when notifications are colliding with it.
      */
-    private void updateKeyguardHeaderVisibility() {
+    private void updateHeader() {
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
                 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
-            boolean hidden;
-            if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
-                
-                // When on Keyguard, we hide the header as soon as the top card of the notification
-                // stack scroller is close enough (collision distance) to the bottom of the header.
-                hidden = mNotificationStackScroller.getNotificationsTopY()
-                        <= mHeader.getBottom() + mNotificationsHeaderCollideDistance;
-            } else {
-
-                // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
-                // soon as we start translating the stack.
-                hidden = mNotificationStackScroller.getTranslationY() < 0;
-            }
-
-            if (hidden && !mHeaderHidden) {
-                mHeader.animate()
-                        .alpha(0f)
-                        .withLayer()
-                        .translationY(-mHeader.getHeight()/2)
-                        .setInterpolator(mFastOutLinearInterpolator)
-                        .setDuration(200);
-            } else if (!hidden && mHeaderHidden) {
-                mHeader.animate()
-                        .alpha(1f)
-                        .withLayer()
-                        .translationY(0)
-                        .setInterpolator(mLinearOutSlowInInterpolator)
-                        .setDuration(200);
-            }
-            mHeaderHidden = hidden;
+            updateHeaderKeyguard();
         } else {
-            mHeader.animate().cancel();
-            mHeader.setAlpha(1f);
-            mHeader.setTranslationY(0f);
-            if (mHeader.getLayerType() != LAYER_TYPE_NONE) {
-                mHeader.setLayerType(LAYER_TYPE_NONE, null);
-            }
-            mHeaderHidden = false;
+            updateHeaderShade();
         }
 
     }
 
+    private void updateHeaderShade() {
+        mHeader.setAlpha(1f);
+        mHeader.setTranslationY(getHeaderTranslation());
+        setQsTranslation(mQsExpansionHeight);
+    }
+
+    private float getHeaderTranslation() {
+        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
+                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
+            return 0;
+        }
+        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
+            if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
+                return 0;
+            } else {
+                return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
+            }
+        }
+        return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
+    }
+
+    private void updateHeaderKeyguard() {
+        mHeader.setTranslationY(0f);
+        float alpha;
+        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+
+            // When on Keyguard, we hide the header as soon as the top card of the notification
+            // stack scroller is close enough (collision distance) to the bottom of the header.
+            alpha = mNotificationStackScroller.getNotificationsTopY()
+                    /
+                    (mQsMinExpansionHeight + mNotificationsHeaderCollideDistance);
+
+        } else {
+
+            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
+            // soon as we start translating the stack.
+            alpha = mNotificationStackScroller.getNotificationsTopY() / mQsMinExpansionHeight;
+        }
+        alpha = Math.max(0, Math.min(alpha, 1));
+        alpha = (float) Math.pow(alpha, 0.75);
+        mHeader.setAlpha(alpha);
+        mKeyguardBottomArea.setAlpha(alpha);
+        setQsTranslation(mQsExpansionHeight);
+    }
+
     @Override
     protected void onExpandingStarted() {
         super.onExpandingStarted();
@@ -1007,4 +1037,13 @@
                 ? mKeyguardBottomArea.getPhoneImageView()
                 : mKeyguardBottomArea.getCameraImageView();
     }
+
+    @Override
+    protected float getPeekHeight() {
+        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
+            return mNotificationStackScroller.getPeekHeight();
+        } else {
+            return mQsMinExpansionHeight;
+        }
+    }
 }