Improved the transition into the statusbar

When expanding, the shelf now transforms into
the statusbar, providing a more seemless transition.

This also modifies the panel peeking:
previously the panel was always peeking right after a tap timeout
which would take the panel away from the users finger.
The panel is now only peeking when the user clicked on the bar instead.

Test: Add some notification and collapse the panel. Observe nicer transition
Bug: 32437839
Change-Id: I772f6684e1cee2004e9b366d203a5c5188af4a93
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 1a69492..ff644ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -50,6 +50,8 @@
     private int[] mTmp = new int[2];
     private boolean mHideBackground;
     private int mIconAppearTopPadding;
+    private int mStatusBarHeight;
+    private int mStatusBarPaddingStart;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -75,6 +77,9 @@
     private void initDimens() {
         mIconAppearTopPadding = getResources().getDimensionPixelSize(
                 R.dimen.notification_icon_appear_padding);
+        mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
+        mStatusBarPaddingStart = getResources().getDimensionPixelOffset(
+                R.dimen.status_bar_padding_start);
     }
 
     @Override
@@ -124,6 +129,11 @@
             mShelfState.yTranslation = Math.min(viewEnd, maxShelfEnd) - mShelfState.height;
             mShelfState.zTranslation = Math.max(mShelfState.zTranslation,
                     ambientState.getBaseZHeight());
+            float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation())
+                    / (getIntrinsicHeight() * 2);
+            openedAmount = Math.min(1.0f, openedAmount);
+            mShelfState.iconContainerTranslation = (1.0f - openedAmount)
+                    * (mStatusBarPaddingStart - mNotificationIconContainer.getLeft());
             mShelfState.clipTopAmount = 0;
             mShelfState.alpha = 1.0f;
             mShelfState.belowShelf = false;
@@ -173,6 +183,10 @@
         }
     }
 
+    private float getFullyClosedTranslation() {
+        return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
+    }
+
     private void updateIconAppearance(NotificationData.Entry entry,
             ExpandableNotificationRow.NotificationViewState rowState,
             ShelfState shelfState) {
@@ -254,15 +268,21 @@
         return super.shouldHideBackground() || mHideBackground;
     }
 
+    private void setIconContainerTranslation(float iconContainerTranslation) {
+        mNotificationIconContainer.setTranslationX(iconContainerTranslation);
+    }
+
     private class ShelfState extends ExpandableViewState {
         private WeakHashMap<View, ViewState> iconStates = new WeakHashMap<>();
         private boolean hideBackground;
+        private float iconContainerTranslation;
 
         @Override
         public void applyToView(View view) {
             super.applyToView(view);
             mNotificationIconContainer.applyIconStates(iconStates);
             setHideBackground(hideBackground);
+            setIconContainerTranslation(iconContainerTranslation);
         }
 
         public void resetIcons() {
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 081ed51..584d5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -207,6 +207,7 @@
         }
     };
     private NotificationGroupManager mGroupManager;
+    private boolean mOpening;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -557,9 +558,7 @@
     protected void flingToHeight(float vel, boolean expand, float target,
             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
         mHeadsUpTouchHelper.notifyFling(!expand);
-        setClosingWithAlphaFadeout(!expand
-                && mNotificationStackScroller.getFirstChildIntrinsicHeight() <= mMaxFadeoutHeight
-                && getFadeoutAlpha() == 1.0f);
+        setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f);
         super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
     }
 
@@ -734,6 +733,11 @@
     }
 
     @Override
+    protected float getOpeningHeight() {
+        return mNotificationStackScroller.getMinExpansionHeight();
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
             return false;
@@ -1426,7 +1430,7 @@
             if (mKeyguardShowing) {
 
                 // On Keyguard, interpolate the QS expansion linearly to the panel expansion
-                t = expandedHeight / getMaxPanelHeight();
+                t = expandedHeight / (getMaxPanelHeight());
             } else {
 
                 // In Shade, interpolate linearly such that QS is closed whenever panel height is
@@ -2276,6 +2280,14 @@
     protected void updateExpandedHeight(float expandedHeight) {
         mNotificationStackScroller.setExpandedHeight(expandedHeight);
         updateKeyguardBottomAreaAlpha();
+        setOpening(expandedHeight <= getOpeningHeight());
+    }
+
+    private void setOpening(boolean opening) {
+        if (opening != mOpening) {
+            mOpening = opening;
+            mStatusBar.recomputeDisableFlags(false);
+        }
     }
 
     public void setPanelScrimMinFraction(float minFraction) {
@@ -2327,7 +2339,18 @@
     @Override
     public void setAlpha(float alpha) {
         super.setAlpha(alpha);
-        mNotificationStackScroller.setParentFadingOut(alpha != 1.0f);
+        updateFullyVisibleState();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        updateFullyVisibleState();
+    }
+
+    private void updateFullyVisibleState() {
+        mNotificationStackScroller.setParentNotFullyVisible(getAlpha() != 1.0f
+                || getVisibility() != VISIBLE);
     }
 
     /**
@@ -2369,6 +2392,15 @@
         mGroupManager = groupManager;
     }
 
+    public boolean shouldHideNotificationIcons() {
+        return !mOpening && !isFullyCollapsed();
+    }
+
+    public boolean shouldAnimateIconHiding() {
+        // TODO: handle this correctly, not completely working yet
+        return mNotificationStackScroller.getTranslationX() != 0;
+    }
+
     private final FragmentListener mFragmentListener = new FragmentListener() {
         @Override
         public void onFragmentViewCreated(String tag, Fragment fragment) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index f2c57e5..87a3848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -118,7 +118,7 @@
         boolean fullyOpened = false;
         if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
         PanelView pv = mPanel;
-        pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
+        pv.setVisibility(expanded ? VISIBLE : INVISIBLE);
         // adjust any other panels that may be partially visible
         if (expanded) {
             if (mState == STATE_CLOSED) {
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 3de03b5..570d5d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -50,6 +51,10 @@
 public abstract class PanelView extends FrameLayout {
     public static final boolean DEBUG = PanelBar.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
+    private static final int INITIAL_OPENING_PEEK_DURATION = 200;
+    private static final int PEEK_ANIMATION_DURATION = 360;
+    private long mDownTime;
+    private float mMinExpandHeight;
 
     private final void logf(String fmt, Object... args) {
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -88,6 +93,7 @@
     private ObjectAnimator mPeekAnimator;
     private VelocityTrackerInterface mVelocityTracker;
     private FlingAnimationUtils mFlingAnimationUtils;
+    private FlingAnimationUtils mFlingAnimationUtilsClosing;
     private FalsingManager mFalsingManager;
 
     /**
@@ -106,9 +112,6 @@
     private Interpolator mBounceInterpolator;
     protected KeyguardBottomAreaView mKeyguardBottomArea;
 
-    private boolean mPeekPending;
-    private boolean mCollapseAfterPeek;
-
     /**
      * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
      */
@@ -118,13 +121,6 @@
     private boolean mGestureWaitForTouchSlop;
     private boolean mIgnoreXTouchSlop;
     private boolean mExpandLatencyTracking;
-    private Runnable mPeekRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mPeekPending = false;
-            runPeekAnimation();
-        }
-    };
 
     protected void onExpandingFinished() {
         mBar.onExpandingFinished();
@@ -148,21 +144,17 @@
         }
     }
 
-    private void schedulePeek() {
-        mPeekPending = true;
-        long timeout = ViewConfiguration.getTapTimeout();
-        postOnAnimationDelayed(mPeekRunnable, timeout);
-        notifyBarPanelExpansionChanged();
-    }
-
-    private void runPeekAnimation() {
-        mPeekHeight = getPeekHeight();
+    private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
+        mPeekHeight = peekHeight;
         if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
         if (mHeightAnimator != null) {
             return;
         }
+        if (mPeekAnimator != null) {
+            mPeekAnimator.cancel();
+        }
         mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight)
-                .setDuration(250);
+                .setDuration(duration);
         mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
         mPeekAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
@@ -175,10 +167,10 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mPeekAnimator = null;
-                if (mCollapseAfterPeek && !mCancelled) {
+                if (!mCancelled && collapseWhenFinished) {
                     postOnAnimation(mPostCollapseRunnable);
                 }
-                mCollapseAfterPeek = false;
+
             }
         });
         notifyExpandingStarted();
@@ -189,6 +181,7 @@
     public PanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
+        mFlingAnimationUtilsClosing = new FlingAnimationUtils(context, 0.4f);
         mBounceInterpolator = new BounceInterpolator();
         mFalsingManager = FalsingManager.getInstance(context);
     }
@@ -267,11 +260,13 @@
             case MotionEvent.ACTION_DOWN:
                 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                 mJustPeeked = false;
+                mMinExpandHeight = 0.0f;
                 mPanelClosedOnDown = isFullyCollapsed();
                 mHasLayoutedSinceDown = false;
                 mUpdateFlingOnLayout = false;
                 mMotionAborted = false;
                 mPeekTouching = mPanelClosedOnDown;
+                mDownTime = SystemClock.uptimeMillis();
                 mTouchAboveFalsingThreshold = false;
                 mCollapsedAndHeadsUpOnDown = isFullyCollapsed()
                         && mHeadsUpManager.hasPinnedHeadsUp();
@@ -279,16 +274,16 @@
                     initVelocityTracker();
                 }
                 trackMovement(event);
-                if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
-                        mPeekPending || mPeekAnimator != null) {
+                if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)
+                        || mPeekAnimator != null) {
                     cancelHeightAnimator();
                     cancelPeek();
                     mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
-                            || mPeekPending || mPeekAnimator != null;
+                            || mPeekAnimator != null;
                     onTrackingStarted();
                 }
                 if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()) {
-                    schedulePeek();
+                    startOpening();
                 }
                 break;
 
@@ -317,7 +312,7 @@
                 // y-component of the gesture, as we have no conflicting horizontal gesture.
                 if (Math.abs(h) > mTouchSlop
                         && (Math.abs(h) > Math.abs(x - mInitialTouchX)
-                                || mIgnoreXTouchSlop)) {
+                        || mIgnoreXTouchSlop)) {
                     mTouchSlopExceeded = true;
                     if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
                         if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
@@ -325,23 +320,30 @@
                             h = 0;
                         }
                         cancelHeightAnimator();
-                        removeCallbacks(mPeekRunnable);
-                        mPeekPending = false;
                         onTrackingStarted();
                     }
                 }
-                final float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+                float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
                 if (newHeight > mPeekHeight) {
                     if (mPeekAnimator != null) {
                         mPeekAnimator.cancel();
                     }
                     mJustPeeked = false;
+                } else if (mPeekAnimator == null && mJustPeeked) {
+                    // The initial peek has finished, but we haven't dragged as far yet, lets
+                    // speed it up by starting at the peek height.
+                    mInitialOffsetOnTouch = mExpandedHeight;
+                    mInitialTouchY = y;
+                    mMinExpandHeight = mExpandedHeight;
+                    mJustPeeked = false;
                 }
+                newHeight = Math.max(newHeight, mMinExpandHeight);
                 if (-h >= getFalsingThreshold()) {
                     mTouchAboveFalsingThreshold = true;
                     mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
                 }
-                if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
+                if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) &&
+                        !isTrackingBlocked()) {
                     setExpandedHeightInternal(newHeight);
                 }
 
@@ -357,6 +359,14 @@
         return !mGestureWaitForTouchSlop || mTracking;
     }
 
+    private void startOpening() {;
+        runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
+                false /* collapseWhenFinished */);
+        notifyBarPanelExpansionChanged();
+    }
+
+    protected abstract float getOpeningHeight();
+
     /**
      * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
      * horizontal direction
@@ -418,6 +428,15 @@
             if (mUpdateFlingOnLayout) {
                 mUpdateFlingVelocity = vel;
             }
+        } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking) {
+            long timePassed = SystemClock.uptimeMillis() - mDownTime;
+            if (timePassed < ViewConfiguration.getLongPressTimeout()) {
+                // Lets show the user that he can actually expand the panel
+                runPeekAnimation(PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
+            } else {
+                // We need to collapse the panel since we peeked to the small height.
+                postOnAnimation(mPostCollapseRunnable);
+            }
         } else {
             boolean expands = onEmptySpaceClick(mInitialTouchX);
             onTrackingStopped(expands);
@@ -448,7 +467,6 @@
     protected void onTrackingStarted() {
         endClosing();
         mTracking = true;
-        mCollapseAfterPeek = false;
         mBar.onTrackingStarted();
         notifyExpandingStarted();
         notifyBarPanelExpansionChanged();
@@ -482,7 +500,10 @@
             case MotionEvent.ACTION_DOWN:
                 mStatusBar.userActivity();
                 mAnimatingOnDown = mHeightAnimator != null;
-                if (mAnimatingOnDown && mClosing && !mHintAnimationRunning || mPeekPending || mPeekAnimator != null) {
+                mMinExpandHeight = 0.0f;
+                mDownTime = SystemClock.uptimeMillis();
+                if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
+                        || mPeekAnimator != null) {
                     cancelHeightAnimator();
                     cancelPeek();
                     mTouchSlopExceeded = true;
@@ -639,7 +660,7 @@
     protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
             boolean expandBecauseOfFalsing) {
         cancelPeek();
-        float target = expand ? getMaxPanelHeight() : 0.0f;
+        float target = expand ? getMaxPanelHeight() : 0;
         if (!expand) {
             mClosing = true;
         }
@@ -672,8 +693,7 @@
                 animator.setDuration(350);
             }
         } else {
-            mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel,
-                    getHeight());
+            mFlingAnimationUtilsClosing.apply(animator, mExpandedHeight, target, vel, getHeight());
 
             // Make it shorter if we run a canned animation
             if (vel == 0) {
@@ -742,7 +762,6 @@
                 && mHeightAnimator == null
                 && !isFullyCollapsed()
                 && currentMaxPanelHeight != mExpandedHeight
-                && !mPeekPending
                 && mPeekAnimator == null
                 && !mPeekTouching) {
             setExpandedHeight(currentMaxPanelHeight);
@@ -769,10 +788,8 @@
             }
         }
 
-        mExpandedHeight = Math.max(0, mExpandedHeight);
-        mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
-                ? 0
-                : mExpandedHeight / fhWithoutOverExpansion);
+        mExpandedFraction = Math.min(1f,
+                fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
         onHeightUpdated(mExpandedHeight);
         notifyBarPanelExpansionChanged();
     }
@@ -816,7 +833,7 @@
     }
 
     public boolean isFullyCollapsed() {
-        return mExpandedHeight <= 0;
+        return mExpandedFraction <= 0.0f;
     }
 
     public boolean isCollapsing() {
@@ -833,16 +850,7 @@
 
     public void collapse(boolean delayed, float speedUpFactor) {
         if (DEBUG) logf("collapse: " + this);
-        if (mPeekPending || mPeekAnimator != null) {
-            mCollapseAfterPeek = true;
-            if (mPeekPending) {
-
-                // We know that the whole gesture is just a peek triggered by a simple click, so
-                // better start it now.
-                removeCallbacks(mPeekRunnable);
-                mPeekRunnable.run();
-            }
-        } else if (!isFullyCollapsed() && !mTracking && !mClosing) {
+        if (!isFullyCollapsed() && !mTracking && !mClosing) {
             cancelHeightAnimator();
             notifyExpandingStarted();
 
@@ -866,13 +874,11 @@
     };
 
     public void cancelPeek() {
-        boolean cancelled = mPeekPending;
+        boolean cancelled = false;
         if (mPeekAnimator != null) {
             cancelled = true;
             mPeekAnimator.cancel();
         }
-        removeCallbacks(mPeekRunnable);
-        mPeekPending = false;
 
         if (cancelled) {
             // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
@@ -1048,7 +1054,7 @@
     }
 
     protected void notifyBarPanelExpansionChanged() {
-        mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f || mPeekPending
+        mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f
                 || mPeekAnimator != null || mInstantExpanding || isPanelVisibleBecauseOfHeadsUp()
                 || mTracking || mHeightAnimator != null);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index fb51f51..604e8e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1054,7 +1054,7 @@
     private void inflateShelf() {
         mNotificationShelf =
                 (NotificationShelf) LayoutInflater.from(mContext).inflate(
-                        R.layout.status_bar_notification_icon_container, mStackScroller, false);
+                        R.layout.status_bar_notification_shelf, mStackScroller, false);
         mNotificationShelf.setOnActivatedListener(this);
         mNotificationShelf.setOnClickListener(mShelfClickListener);
         mStackScroller.setShelf(mNotificationShelf);
@@ -2005,10 +2005,12 @@
             }
             currentIndex++;
         }
+        boolean noAmbient = false;
         if (shelfIndex == -1) {
             shelfIndex = currentIndex;
+            noAmbient = true;
         }
-        mStackScroller.updateShelfIndex(shelfIndex);
+        mStackScroller.updateShelfIndex(shelfIndex, noAmbient);
     }
 
     public static boolean isTopLevelChild(Entry entry) {
@@ -2380,8 +2382,7 @@
     }
 
     protected int adjustDisableFlags(int state) {
-        if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway
-                && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) {
+        if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway && shouldHideNotificationIcons()) {
             state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
             state |= StatusBarManager.DISABLE_SYSTEM_INFO;
         }
@@ -2396,6 +2397,10 @@
         return state;
     }
 
+    private boolean shouldHideNotificationIcons() {
+        return mExpandedVisible && mNotificationPanel.shouldHideNotificationIcons();
+    }
+
     /**
      * State is one or more of the DISABLE constants from StatusBarManager.
      */
@@ -2502,7 +2507,7 @@
      *
      * This needs to be called if state used by {@link #adjustDisableFlags} changes.
      */
-    private void recomputeDisableFlags(boolean animate) {
+    public void recomputeDisableFlags(boolean animate) {
         disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
     }
 
@@ -2948,7 +2953,7 @@
         runPostCollapseRunnables();
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         showBouncer();
-        recomputeDisableFlags(true /* animate */);
+        recomputeDisableFlags(shouldAnimatIconHiding() /* animate */);
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -2957,6 +2962,10 @@
         }
     }
 
+    private boolean shouldAnimatIconHiding() {
+        return mNotificationPanel.shouldAnimateIconHiding();
+    }
+
     public boolean interceptTouchEvent(MotionEvent event) {
         if (DEBUG_GESTURES) {
             if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -4166,7 +4175,7 @@
                 mScrimController.forceHideScrims(true /* hide */);
                 updateMediaMetaData(false, true);
                 mNotificationPanel.setAlpha(1);
-                mStackScroller.setParentFadingOut(true);
+                mStackScroller.setParentNotFullyVisible(true);
                 mNotificationPanel.animate()
                         .alpha(0)
                         .setStartDelay(FADE_KEYGUARD_START_DELAY)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 6217433..f6dd88d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -235,7 +235,7 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN
-                && mNotificationPanel.getExpandedHeight() == 0f) {
+                && mNotificationPanel.isFullyCollapsed()) {
             mNotificationPanel.startExpandLatencyTracking();
         }
         mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
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 39b56d7..d046723 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -333,7 +333,7 @@
     private boolean mPulsing;
     private boolean mDrawBackgroundAsSrc;
     private boolean mFadingOut;
-    private boolean mParentFadingOut;
+    private boolean mParentNotFullyVisible;
     private boolean mGroupExpandedForMeasure;
     private boolean mScrollable;
     private View mForcedScroll;
@@ -355,6 +355,10 @@
     private boolean mBackwardScrollable;
     private NotificationShelf mShelf;
     private int mMaxDisplayedNotifications = -1;
+    private int mStatusBarHeight;
+    private boolean mNoAmbient;
+    private final Rect mClipRect = new Rect();
+    private boolean mIsClipped;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -472,6 +476,7 @@
         mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
         mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
                 R.dimen.min_top_overscroll_to_qs);
+        mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
     }
 
     public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -480,7 +485,7 @@
     }
 
     private void updateSrcDrawing() {
-        mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut)
+        mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible
                 ? mSrcMode : null);
         invalidate();
     }
@@ -532,9 +537,10 @@
         }
     }
 
-    public void updateShelfIndex(int newIndex) {
+    public void updateShelfIndex(int newIndex, boolean noAmbient) {
         mAmbientState.setShelfIndex(newIndex);
         changeViewPosition(mShelf, newIndex);
+        mNoAmbient = noAmbient;
     }
 
     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
@@ -668,6 +674,16 @@
         }
     }
 
+    @Override
+    public void setClipBounds(Rect clipBounds) {
+        super.setClipBounds(clipBounds);
+        boolean clipped = clipBounds != null;
+        if (clipped != mIsClipped) {
+            mIsClipped = clipped;
+            updateFadingState();
+        }
+    }
+
     /**
      * Update the height of the panel.
      *
@@ -675,7 +691,18 @@
      */
     public void setExpandedHeight(float height) {
         mExpandedHeight = height;
-        setIsExpanded(height > 0.0f);
+        int minExpansionHeight = getMinExpansionHeight();
+        if (height < minExpansionHeight) {
+            mClipRect.left = 0;
+            mClipRect.right = getWidth();
+            mClipRect.top = 0;
+            mClipRect.bottom = (int) height;
+            height = minExpansionHeight;
+            setClipBounds(mClipRect);
+        } else {
+            setClipBounds(null);
+        }
+        setIsExpanded(height > getMinExpansionHeight());
         int stackHeight;
         float translationY;
         float appearEndPosition = getAppearEndPosition();
@@ -708,13 +735,7 @@
      *         Measured relative to the resting position.
      */
     private float getExpandTranslationStart() {
-        int startPosition = 0;
-        if (!mTrackingHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
-            startPosition = - Math.min(getFirstChildIntrinsicHeight(),
-                    mMaxLayoutHeight - mIntrinsicPadding - mBottomStackSlowDownHeight
-                            - mBottomStackPeekSize);
-        }
-        return startPosition - mTopPadding;
+        return - mTopPadding;
     }
 
     /**
@@ -722,9 +743,7 @@
      *         Measured in absolute height.
      */
     private float getAppearStartPosition() {
-        return mTrackingHeadsUp
-                ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
-                : 0;
+        return getMinExpansionHeight();
     }
 
     /**
@@ -735,7 +754,7 @@
         int firstItemHeight = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
                 ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize
                         + mBottomStackSlowDownHeight
-                : getLayoutMinHeight();
+                : getFirstItemMinHeight() + mShelf.getIntrinsicHeight();
         return firstItemHeight + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
     }
 
@@ -2052,7 +2071,7 @@
 
     private void applyCurrentBackgroundBounds() {
         mScrimController.setExcludedBackgroundArea(
-                mFadingOut || mParentFadingOut || mAmbientState.isDark() ? null
+                mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
                         : mCurrentBounds);
         invalidate();
     }
@@ -2131,8 +2150,8 @@
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
             View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE
-                    && child instanceof ActivatableNotificationView) {
+            if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+                    && child != mShelf) {
                 return (ActivatableNotificationView) child;
             }
         }
@@ -2143,8 +2162,8 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE
-                    && child instanceof ActivatableNotificationView) {
+            if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+                    && child != mShelf) {
                 return (ActivatableNotificationView) child;
             }
         }
@@ -2227,9 +2246,7 @@
     }
 
     public int getLayoutMinHeight() {
-        int firstChildMinHeight = getFirstChildIntrinsicHeight();
-        return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
-                mMaxLayoutHeight - mIntrinsicPadding);
+        return mShelf.getIntrinsicHeight();
     }
 
     public int getFirstChildIntrinsicHeight() {
@@ -3095,6 +3112,11 @@
 
     public int getEmptyBottomMargin() {
         int emptyMargin = mMaxLayoutHeight - mContentHeight;
+        if (!mNoAmbient || mEmptyShadeView.getVisibility() == VISIBLE) {
+            // If we have ambient notifications or a clear all button, we need to make sure that the
+            // notifications are a bit taller, otherwise they will be pushed under the other cards
+            emptyMargin -= (mBottomStackPeekSize + mBottomStackSlowDownHeight);
+        }
         return Math.max(emptyMargin, 0);
     }
 
@@ -3876,9 +3898,13 @@
         }
     }
 
-    public void setParentFadingOut(boolean fadingOut) {
-        if (fadingOut != mParentFadingOut) {
-            mParentFadingOut = fadingOut;
+    public void setParentNotFullyVisible(boolean parentNotFullyVisible) {
+        if (mScrimController == null) {
+            // we're not set up yet.
+            return;
+        }
+        if (parentNotFullyVisible != mParentNotFullyVisible) {
+            mParentNotFullyVisible = parentNotFullyVisible;
             updateFadingState();
         }
     }
@@ -3938,6 +3964,13 @@
         }
     }
 
+    public int getMinExpansionHeight() {
+        return mTrackingHeadsUp
+                ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
+                : mShelf.getIntrinsicHeight()
+                        - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index b8acb8c..1b3afe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -379,12 +379,6 @@
             }
         }
 
-        if (i == 0 && ambientState.getScrollY() <= 0) {
-            // The first card can get into the bottom stack if it's the only one
-            // on the lockscreen which pushes it up. Let's make sure that doesn't happen and
-            // it stays at the top
-            childViewState.yTranslation = Math.max(0, childViewState.yTranslation);
-        }
         currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
         if (currentYPosition <= 0) {
             childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;