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/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index aa62daa..ec62ae1 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -48,28 +48,36 @@
         android:orientation="horizontal"
         >
 
-        <LinearLayout
+        <com.android.systemui.statusbar.AlphaOptimizedLinearLayout
             android:id="@+id/notification_icon_area"
             android:layout_width="0dip"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:orientation="horizontal"
             >
-            <com.android.systemui.statusbar.StatusBarIconView android:id="@+id/moreIcon"
-                android:layout_width="@dimen/status_bar_icon_size"
-                android:layout_height="match_parent"
-                android:src="@drawable/stat_notify_more"
-                android:visibility="gone"
-                />
-            <com.android.systemui.statusbar.phone.IconMerger android:id="@+id/notificationIcons"
+            <!-- The alpha of this area is both controlled from PhoneStatusBarTransitions and
+                 PhoneStatusBar (DISABLE_NOTIFICATION_ICONS), so we need two views here. -->
+            <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+                android:id="@+id/notification_icon_area_inner"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:layout_alignParentStart="true"
-                android:gravity="center_vertical"
-                android:orientation="horizontal"/>  
-        </LinearLayout>
+                >
+                <com.android.systemui.statusbar.StatusBarIconView android:id="@+id/moreIcon"
+                    android:layout_width="@dimen/status_bar_icon_size"
+                    android:layout_height="match_parent"
+                    android:src="@drawable/stat_notify_more"
+                    android:visibility="gone"
+                    />
+                <com.android.systemui.statusbar.phone.IconMerger android:id="@+id/notificationIcons"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_alignParentStart="true"
+                    android:gravity="center_vertical"
+                    android:orientation="horizontal"/>
+            </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+        </com.android.systemui.statusbar.AlphaOptimizedLinearLayout>
 
-        <LinearLayout android:id="@+id/system_icon_area"
+        <com.android.systemui.statusbar.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:orientation="horizontal"
@@ -109,7 +117,7 @@
                 android:paddingStart="6dip"
                 android:gravity="center_vertical|start"
                 />
-        </LinearLayout>
+        </com.android.systemui.statusbar.AlphaOptimizedLinearLayout>
     </LinearLayout>
 
     <ViewStub
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index b54ba1a..2b7793d 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -56,6 +56,12 @@
         android:clipToPadding="false"
         android:clipChildren="false">
 
+        <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
+            android:id="@+id/notification_stack_scroller"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginBottom="@dimen/close_handle_underlap"/>
+
         <com.android.systemui.statusbar.phone.ObservableScrollView
             android:id="@+id/scroll_view"
             android:layout_width="match_parent"
@@ -82,12 +88,6 @@
             </LinearLayout>
         </com.android.systemui.statusbar.phone.ObservableScrollView>
 
-        <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
-            android:id="@+id/notification_stack_scroller"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_marginBottom="@dimen/close_handle_underlap"/>
-
     </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
 
     <include layout="@layout/status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f596edc..1447849 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -155,10 +155,6 @@
     <!-- The size of the gesture span needed to activate the "pull" notification expansion -->
     <dimen name="pull_span_min">25dp</dimen>
 
-    <!-- How far to slide the panel out when you touch it -->
-    <!-- For phones, this is close_handle_height + header_height -->
-    <dimen name="peek_height">84dp</dimen>
-
     <dimen name="qs_tile_height">88dp</dimen>
     <dimen name="qs_tile_icon_size">28dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
new file mode 100644
index 0000000..a835c0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+/**
+ * A frame layout which does not have overlapping renderings commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class AlphaOptimizedFrameLayout extends FrameLayout
+{
+    public AlphaOptimizedFrameLayout(Context context) {
+        super(context);
+    }
+
+    public AlphaOptimizedFrameLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AlphaOptimizedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AlphaOptimizedFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedLinearLayout.java
new file mode 100644
index 0000000..9f4c3a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedLinearLayout.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * A linear layout which does not have overlapping renderings commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class AlphaOptimizedLinearLayout extends LinearLayout
+{
+    public AlphaOptimizedLinearLayout(Context context) {
+        super(context);
+    }
+
+    public AlphaOptimizedLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AlphaOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AlphaOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index c621f31..3690701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -425,7 +425,7 @@
 
         createAndAddWindows();
 
-        disable(switches[0]);
+        disable(switches[0], false /* animate */);
         setSystemUiVisibility(switches[1], 0xffffffff);
         topAppWindowChanged(switches[2] != 0);
         // StatusBarManagerService has a back up of IME token and it's restored here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 2b61f09..eb44002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -19,7 +19,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
-import android.service.notification.StatusBarNotification;
 
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.StatusBarIcon;
@@ -77,7 +76,7 @@
         public void updateIcon(String slot, int index, int viewIndex,
                 StatusBarIcon old, StatusBarIcon icon);
         public void removeIcon(String slot, int index, int viewIndex);
-        public void disable(int state);
+        public void disable(int state, boolean animate);
         public void animateExpandNotificationsPanel();
         public void animateCollapsePanels(int flags);
         public void animateExpandSettingsPanel();
@@ -254,7 +253,7 @@
                     break;
                 }
                 case MSG_DISABLE:
-                    mCallbacks.disable(msg.arg1);
+                    mCallbacks.disable(msg.arg1, true /* animate */);
                     break;
                 case MSG_EXPAND_NOTIFICATIONS:
                     mCallbacks.animateExpandNotificationsPanel();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index c3fb83c..0a5d138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -123,6 +123,7 @@
      * @param notifyListeners Whether the listener should be informed about the change.
      */
     public void setActualHeight(int actualHeight, boolean notifyListeners) {
+        mActualHeightInitialized = true;
         mActualHeight = actualHeight;
         if (notifyListeners) {
             notifyHeightChanged();
@@ -130,7 +131,6 @@
     }
 
     public void setActualHeight(int actualHeight) {
-        mActualHeightInitialized = true;
         setActualHeight(actualHeight, true);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 5f1325b5..9968bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -29,8 +29,9 @@
 public class FlingAnimationUtils {
 
     private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
-    private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.7f;
-    private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 1f;
+    private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
+    private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
+    private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
     private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
     private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
 
@@ -203,9 +204,8 @@
         float velAbs = Math.abs(velocity);
         float y2 = calculateLinearOutFasterInY2(velAbs);
 
-        // The gradient at the start of the curve is just y2.
-        float startGradient = y2;
-        Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, 1, y2);
+        float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
+        Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
         float durationSeconds = startGradient * diff / velAbs;
         if (durationSeconds <= maxLengthSeconds) {
             mAnimatorProperties.interpolator = mLinearOutFasterIn;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 63698e4..b5f38ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -238,6 +238,11 @@
     }
 
     @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    @Override
     public void onMethodSecureChanged(boolean methodSecure) {
         updateTrust();
         updateCameraVisibility();
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;
+        }
+    }
 }
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 12aa004..1e45ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -93,15 +93,14 @@
     }
 
     private void runPeekAnimation() {
+        mPeekHeight = getPeekHeight();
         if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
         if (mHeightAnimator != null) {
             return;
         }
-        if (mPeekAnimator == null) {
-            mPeekAnimator = ObjectAnimator.ofFloat(this,
-                    "expandedHeight", mPeekHeight)
-                .setDuration(250);
-        }
+        mPeekAnimator = ObjectAnimator.ofFloat(this,
+                "expandedHeight", mPeekHeight)
+            .setDuration(250);
         mPeekAnimator.start();
     }
 
@@ -115,9 +114,6 @@
 
     protected void loadDimens() {
         final Resources res = getContext().getResources();
-        mPeekHeight = res.getDimension(R.dimen.peek_height)
-            + getPaddingBottom(); // our window might have a dropshadow
-
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
         mHintDistance = res.getDimension(R.dimen.hint_move_distance);
@@ -758,4 +754,6 @@
     }
 
     public abstract void resetViews();
+
+    protected abstract float getPeekHeight();
 }
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 19c090c..f70010e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -31,6 +31,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
@@ -221,6 +222,8 @@
     LinearLayout mStatusIcons;
     // the icons themselves
     IconMerger mNotificationIcons;
+    View mNotificationIconArea;
+
     // [+>
     View mMoreIcon;
 
@@ -300,16 +303,6 @@
         : null;
 
     private int mNavigationIconHints = 0;
-    private final Animator.AnimatorListener mMakeIconsInvisible = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            // double-check to avoid races
-            if (mStatusBarContents.getAlpha() == 0) {
-                if (DEBUG) Log.d(TAG, "makeIconsInvisible");
-                mStatusBarContents.setVisibility(View.INVISIBLE);
-            }
-        }
-    };
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
     private boolean mUserSetup = false;
@@ -383,8 +376,8 @@
     private boolean mDozing;
 
     private Interpolator mLinearOutSlowIn;
-    private Interpolator mAlphaOut = new PathInterpolator(0f, 0.4f, 1f, 1f);
-    private Interpolator mAlphaIn = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    private Interpolator mAlphaIn = new PathInterpolator(0f, 0.2f, 1f, 1f);
+    private Interpolator mAlphaOut = new PathInterpolator(0f, 0f, 0.8f, 1f);
 
     private final OnChildLocationsChangedListener mOnChildLocationsChangedListener =
             new OnChildLocationsChangedListener() {
@@ -608,6 +601,7 @@
         mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area);
         mSystemIcons = (LinearLayout) mStatusBarView.findViewById(R.id.system_icons);
         mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons);
+        mNotificationIconArea = mStatusBarView.findViewById(R.id.notification_icon_area_inner);
         mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons);
         mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon);
         mNotificationIcons.setOverflowIndicator(mMoreIcon);
@@ -1423,7 +1417,7 @@
     /**
      * State is one or more of the DISABLE constants from StatusBarManager.
      */
-    public void disable(int state) {
+    public void disable(int state, boolean animate) {
         mDisabledUnmodified = state;
         state = adjustDisableFlags(state);
         final int old = mDisabled;
@@ -1461,9 +1455,9 @@
         if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
             mSystemIconArea.animate().cancel();
             if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
-                animateStatusBarHide(mSystemIconArea);
+                animateStatusBarHide(mSystemIconArea, animate);
             } else {
-                animateStatusBarShow(mSystemIconArea);
+                animateStatusBarShow(mSystemIconArea, animate);
             }
         }
 
@@ -1496,9 +1490,9 @@
                 if (mTicking) {
                     haltTicker();
                 }
-                animateStatusBarHide(mNotificationIcons);
+                animateStatusBarHide(mNotificationIconArea, animate);
             } else {
-                animateStatusBarShow(mNotificationIcons);
+                animateStatusBarShow(mNotificationIconArea, animate);
             }
         }
     }
@@ -1506,35 +1500,49 @@
     /**
      * Animates {@code v}, a view that is part of the status bar, out.
      */
-    private void animateStatusBarHide(View v) {
+    private void animateStatusBarHide(final View v, boolean animate) {
+        v.animate().cancel();
+        if (!animate) {
+            v.setAlpha(0f);
+            v.setVisibility(View.INVISIBLE);
+            return;
+        }
         v.animate()
                 .alpha(0f)
-                .withLayer()
                 .setDuration(160)
-                .setInterpolator(mAlphaIn)
                 .setStartDelay(0)
-                .setListener(mMakeIconsInvisible)
-                .start();
+                .setInterpolator(mAlphaOut)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        v.setVisibility(View.INVISIBLE);
+                    }
+                });
     }
 
     /**
      * Animates {@code v}, a view that is part of the status bar, in.
      */
-    private void animateStatusBarShow(View v) {
+    private void animateStatusBarShow(View v, boolean animate) {
+        v.animate().cancel();
         v.setVisibility(View.VISIBLE);
+        if (!animate) {
+            v.setAlpha(1f);
+            return;
+        }
         v.animate()
                 .alpha(1f)
-                .withLayer()
-                .setInterpolator(mAlphaOut)
                 .setDuration(320)
-                .setStartDelay(0);
+                .setInterpolator(mAlphaIn)
+                .setStartDelay(50);
 
         // Synchronize the motion with the Keyguard fading if necessary.
         if (mKeyguardFadingAway) {
             v.animate()
                     .setDuration(mKeyguardFadingAwayDuration)
                     .setInterpolator(mLinearOutSlowIn)
-                    .setStartDelay(mKeyguardFadingAwayDelay);
+                    .setStartDelay(mKeyguardFadingAwayDelay)
+                    .start();
         }
     }
 
@@ -1636,7 +1644,7 @@
 
         visibilityChanged(true);
         mWaitingForKeyguardExit = false;
-        disable(mDisabledUnmodified);
+        disable(mDisabledUnmodified, !force /* animate */);
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
 
@@ -1810,10 +1818,6 @@
         // Shrink the window to the size of the status bar only
         mStatusBarWindowManager.setStatusBarExpanded(false);
 
-        if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) {
-            setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in);
-        }
-
         // Close any "App info" popups that might have snuck on-screen
         dismissPopups();
 
@@ -1824,7 +1828,7 @@
 
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         showBouncer();
-        disable(mDisabledUnmodified);
+        disable(mDisabledUnmodified, true /* animate */);
     }
 
     public boolean interceptTouchEvent(MotionEvent event) {
@@ -2337,15 +2341,6 @@
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
-    void setNotificationIconVisibility(boolean visible, int anim) {
-        int old = mNotificationIcons.getVisibility();
-        int v = visible ? View.VISIBLE : View.INVISIBLE;
-        if (old != v) {
-            mNotificationIcons.setVisibility(v);
-            mNotificationIcons.startAnimation(loadAnim(anim, null));
-        }
-    }
-
     static final float saturate(float a) {
         return a < 0f ? 0f : (a > 1f ? 1f : a);
     }
@@ -2835,7 +2830,7 @@
         mKeyguardFadingAwayDelay = delay;
         mKeyguardFadingAwayDuration = fadeoutDuration;
         mWaitingForKeyguardExit = false;
-        disable(mDisabledUnmodified);
+        disable(mDisabledUnmodified, true /* animate */);
     }
 
     /**
@@ -3093,7 +3088,7 @@
     @Override
     public void setBouncerShowing(boolean bouncerShowing) {
         super.setBouncerShowing(bouncerShowing);
-        disable(mDisabledUnmodified);
+        disable(mDisabledUnmodified, true /* animate */);
     }
 
     public void onScreenTurnedOff() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 3a17177..3cddcfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -111,7 +111,14 @@
     @Override
     public void onAllPanelsCollapsed() {
         super.onAllPanelsCollapsed();
-        mBar.makeExpandedInvisible();
+
+        // Close the status bar in the next frame so we can show the end of the animation.
+        postOnAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mBar.makeExpandedInvisible();
+            }
+        });
         mLastFullyOpenedPanel = null;
     }
 
@@ -166,27 +173,7 @@
     @Override
     public void panelExpansionChanged(PanelView panel, float frac) {
         super.panelExpansionChanged(panel, frac);
-
-        if (DEBUG) {
-            Log.v(TAG, "panelExpansionChanged: f=" + frac);
-        }
-
         mScrimController.setPanelExpansion(frac);
-
-        // fade out the panel as it gets buried into the status bar to avoid overdrawing the
-        // status bar on the last frame of a close animation
-        final int H = mBar.getStatusBarHeight();
-        final float ph = panel.getExpandedHeight() + panel.getPaddingBottom();
-        float alpha = 1f;
-        if (ph < 2*H) {
-            if (ph < H) alpha = 0f;
-            else alpha = (ph - H) / H;
-            alpha = alpha * alpha; // get there faster
-        }
-        if (panel.getAlpha() != alpha) {
-            panel.setAlpha(alpha);
-        }
-
         mBar.updateCarrierLabelVisibility(false);
         mBar.userActivity();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index cd304d0..9e9de5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -405,4 +405,9 @@
         updateVisibilities();
         updateSystemIconsLayoutParams();
     }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return !mKeyguardShowing || mExpanded;
+    }
 }
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 4220efe..c9a1b28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -420,7 +420,7 @@
         int bottomStackPeekSize = mBottomStackPeekSize;
         int minStackHeight = itemHeight + bottomStackPeekSize;
         int stackHeight;
-        if (newStackHeight - mTopPadding >= minStackHeight) {
+        if (newStackHeight - mTopPadding >= minStackHeight || getNotGoneChildCount() == 0) {
             setTranslationY(0);
             stackHeight = newStackHeight;
         } else {
@@ -1266,6 +1266,10 @@
         setTopPadding(clampPadding((int) start), animate);
     }
 
+    public int getPeekHeight() {
+        return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize;
+    }
+
     private int clampPadding(int desiredPadding) {
         return Math.max(desiredPadding, mIntrinsicPadding);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index e354166..d0f61f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -59,7 +59,7 @@
     }
 
     @Override
-    public void disable(int state) {
+    public void disable(int state, boolean animate) {
     }
 
     @Override