Handling a few more border cases with HUNs

Also does sorting correctly now.
The status bar now allows touches below when a
heads-up is on.
Also fixes a few flashes when a heads up was
dismissed or appeared.

Change-Id: I4d90a07333ad2e5ea2a13704cdc9d9184716681a
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index b9b3388..dc0b0e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1880,7 +1880,7 @@
         }
         boolean applyInPlace = shouldApplyInPlace(entry, n);
         final boolean shouldInterrupt = shouldInterrupt(notification);
-        final boolean alertAgain = shouldInterrupt && alertAgain(entry, n);
+        final boolean alertAgain = alertAgain(entry, n);
         boolean isHeadsUp = shouldInterrupt && alertAgain;
 
         entry.notification = notification;
@@ -1947,8 +1947,12 @@
         if (wasHeadsUp) {
             mHeadsUpManager.updateNotification(entry, alertAgain);
             if (!shouldInterrupt) {
+                // We don't want this to be interrupting anymore, lets remove it
                 mHeadsUpManager.removeNotification(key);
             }
+        } else if (shouldInterrupt && alertAgain) {
+            // This notification was updated to be a heads-up, show it!
+            mHeadsUpManager.showNotification(entry);
         }
     }
 
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 6fe609e..eacc436 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -510,9 +510,6 @@
 
     @Override
     protected void flingToHeight(float vel, boolean expand, float target) {
-        if (!expand && mHeadsUpManager.hasPinnedHeadsUp()) {
-            target = mHeadsUpManager.getHighestPinnedHeadsUp();
-        }
         mHeadsUpTouchHelper.notifyFling(!expand);
         super.flingToHeight(vel, expand, target);
     }
@@ -722,7 +719,7 @@
                 || event.getActionMasked() == MotionEvent.ACTION_UP) {
             mConflictingQsExpansionGesture = false;
         }
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isShadeCollapsed()
                 && mQsExpansionEnabled) {
             mTwoFingerQsExpandPossible = true;
         }
@@ -1774,7 +1771,7 @@
     }
 
     private void updateMaxHeadsUpTranslation() {
-        mNotificationStackScroller.setMaxHeadsUpTranslation(getHeight() - mBottomBarHeight);
+        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mBottomBarHeight);
     }
 
     @Override
@@ -2151,16 +2148,12 @@
 
     @Override
     public void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
-        // TODO: figure out the conditions when not to generate an animation
         mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
-        if (isShadeCollapsed()) {
-            setExpandedHeight(mHeadsUpManager.getHighestPinnedHeadsUp());
-        }
     }
 
-    private boolean isShadeCollapsed() {
-        // TODO: handle this cleaner
-        return mHeader.getTranslationY() + mHeader.getCollapsedHeight() <= 0;
+    @Override
+    protected boolean isShadeCollapsed() {
+        return mExpandedHeight == 0 || mHeadsUpManager.hasPinnedHeadsUp();
     }
 
     @Override
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 1b89b3f..b19535b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -659,7 +659,7 @@
         // If the user isn't actively poking us, let's update the height
         if ((!mTracking || isTrackingBlocked())
                 && mHeightAnimator == null
-                && mExpandedHeight > 0
+                && !isShadeCollapsed()
                 && currentMaxPanelHeight != mExpandedHeight
                 && !mPeekPending
                 && mPeekAnimator == null
@@ -730,6 +730,7 @@
     }
 
     public boolean isFullyCollapsed() {
+        // TODO: look into whether this is still correct with HUN's
         return mExpandedHeight <= 0;
     }
 
@@ -1019,6 +1020,8 @@
      */
     protected abstract int getClearAllHeight();
 
+    protected abstract boolean isShadeCollapsed();
+
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
     }
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 74a26a8..ab4ac8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -160,7 +160,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
+import java.util.TreeSet;
 
 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
@@ -615,7 +615,7 @@
                     R.color.notification_panel_solid_background)));
         }
 
-        mHeadsUpManager = new HeadsUpManager(context);
+        mHeadsUpManager = new HeadsUpManager(context, mNotificationPanel.getViewTreeObserver());
         mHeadsUpManager.setBar(this);
         mHeadsUpManager.addListener(this);
         mHeadsUpManager.addListener(mNotificationPanel);
@@ -1880,10 +1880,9 @@
 
     @Override
     public void escalateHeadsUp() {
-        TreeMap<String, HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getEntries();
-        for (String key : entries.keySet()) {
-            Entry entry = entries.get(key).entry;
-            final StatusBarNotification sbn = entry.notification;
+        TreeSet<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getSortedEntries();
+        for (HeadsUpManager.HeadsUpEntry entry : entries) {
+            final StatusBarNotification sbn = entry.entry.notification;
             final Notification notification = sbn.getNotification();
             if (notification.fullScreenIntent != null) {
                 if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 6e0bf8f..84a9f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -172,11 +172,20 @@
         applyUserActivityTimeout(state);
         applyInputFeatures(state);
         applyFitsSystemWindows(state);
+        applyModalFlag(state);
         if (mLp.copyFrom(mLpChanged) != 0) {
             mWindowManager.updateViewLayout(mStatusBarView, mLp);
         }
     }
 
+    private void applyModalFlag(State state) {
+        if (state.headsUpShowing) {
+            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        } else {
+            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        }
+    }
+
     public void setKeyguardShowing(boolean showing) {
         mCurrentState.keyguardShowing = showing;
         apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index d43af59..85ff59a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -30,16 +30,18 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Stack;
-import java.util.TreeMap;
+import java.util.TreeSet;
 
-public class HeadsUpManager {
+public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener {
     private static final String TAG = "HeadsUpManager";
     private static final boolean DEBUG = false;
     private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
@@ -76,8 +78,8 @@
     private PhoneStatusBar mBar;
     private int mSnoozeLengthMs;
     private ContentObserver mSettingsObserver;
-
-    private TreeMap<String ,HeadsUpEntry> mHeadsUpEntries = new TreeMap<>();
+    private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+    private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>();
     private HashSet<String> mSwipedOutKeys = new HashSet<>();
     private int mUser;
     private Clock mClock;
@@ -86,8 +88,9 @@
     private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
     private boolean mIsExpanded;
     private boolean mHasPinnedHeadsUp;
+    private int[] mTmpTwoArray = new int[2];
 
-    public HeadsUpManager(final Context context) {
+    public HeadsUpManager(final Context context, ViewTreeObserver observer) {
         Resources resources = context.getResources();
         mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay);
         if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
@@ -95,7 +98,7 @@
         mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
         mSnoozeLengthMs = mDefaultSnoozeLengthMs;
         mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
-        mHeadsUpNotificationDecay = 2000000;
+        mHeadsUpNotificationDecay = 200000000/*resources.getInteger(R.integer.heads_up_notification_decay)*/;;
         mClock = new Clock();
         // TODO: shadow mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
 
@@ -116,12 +119,7 @@
                 Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
                 mSettingsObserver);
         if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
-
-        // TODO: investigate whether this is still needed
-//        if (!mHeadsUpEntries.isEmpty()) {
-//             whoops, we're on already!
-//             showNotification(mHeadsUpEntries);
-//        }
+        observer.addOnComputeInternalInsetsListener(this);
     }
 
     public void setBar(PhoneStatusBar bar) {
@@ -144,7 +142,6 @@
         addHeadsUpEntry(headsUp);
         updateNotification(headsUp, true);
         headsUp.setInterruption();
-        updatePinnedHeadsUpState(false);
     }
 
     /**
@@ -164,26 +161,32 @@
     }
 
     private void addHeadsUpEntry(NotificationData.Entry entry) {
-        boolean wasEmpty = mHeadsUpEntries.isEmpty();
         HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+
+        // This will also add the entry to the sortedList
         headsUpEntry.setEntry(entry);
         mHeadsUpEntries.put(entry.key, headsUpEntry);
+        entry.row.setHeadsUp(true);
+        if (!entry.row.isInShade() && mIsExpanded) {
+            headsUpEntry.entry.row.setInShade(true);
+        }
+        updatePinnedHeadsUpState(false);
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.OnHeadsUpStateChanged(entry, true);
         }
         entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        entry.row.setHeadsUp(true);
     }
 
     private void removeHeadsUpEntry(NotificationData.Entry entry) {
         HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+        mSortedEntries.remove(remove);
         mEntryPool.release(remove);
         entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
         entry.row.setHeadsUp(false);
+        updatePinnedHeadsUpState(false);
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.OnHeadsUpStateChanged(entry, false);
         }
-        updatePinnedHeadsUpState(false);
     }
 
     private void updatePinnedHeadsUpState(boolean forceImmediate) {
@@ -238,7 +241,8 @@
      */
     public void releaseAllImmediately() {
         if (DEBUG) Log.v(TAG, "releaseAllImmediately");
-        for (String key: mHeadsUpEntries.keySet()) {
+        HashSet<String> keys = new HashSet<>(mHeadsUpEntries.keySet());
+        for (String key: keys) {
             releaseImmediately(key);
         }
     }
@@ -287,12 +291,12 @@
         return mHeadsUpEntries.get(key).entry;
     }
 
-    public TreeMap<String, HeadsUpEntry> getEntries() {
-        return mHeadsUpEntries;
+    public TreeSet<HeadsUpEntry> getSortedEntries() {
+        return mSortedEntries;
     }
 
     public HeadsUpEntry getTopEntry() {
-        return mHeadsUpEntries.isEmpty() ? null : mHeadsUpEntries.lastEntry().getValue();
+        return mSortedEntries.isEmpty() ? null : mSortedEntries.first();
     }
 
     /**
@@ -313,13 +317,25 @@
     }
 
     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
-        // TODO: Look into touchable region
-//        mContentHolder.getLocationOnScreen(mTmpTwoArray);
-//
-//        info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-//        info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1],
-//                mTmpTwoArray[0] + mContentHolder.getWidth(),
-//                mTmpTwoArray[1] + mContentHolder.getHeight());
+        if (!mIsExpanded && mHasPinnedHeadsUp) {
+            int minX = Integer.MAX_VALUE;
+            int maxX = 0;
+            int minY = Integer.MAX_VALUE;
+            int maxY = 0;
+            for (HeadsUpEntry entry: mSortedEntries) {
+                ExpandableNotificationRow row = entry.entry.row;
+                if (!row.isInShade()) {
+                    row.getLocationOnScreen(mTmpTwoArray);
+                    minX = Math.min(minX, mTmpTwoArray[0]);
+                    minY = Math.min(minY, 0);
+                    maxX = Math.max(maxX, mTmpTwoArray[0] + row.getWidth());
+                    maxY = Math.max(maxY, row.getHeadsUpHeight());
+                }
+            }
+
+            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(minX, minY, maxX, maxY);
+        }
     }
 
     public void setUser(int user) {
@@ -332,8 +348,8 @@
         pw.print("  mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
         pw.print("  now="); pw.println(SystemClock.elapsedRealtime());
         pw.print("  mUser="); pw.println(mUser);
-        for (String key: mHeadsUpEntries.keySet()) {
-            pw.print("  HeadsUpEntry="); pw.println(mHeadsUpEntries.get(key));
+        for (HeadsUpEntry entry: mSortedEntries) {
+            pw.print("  HeadsUpEntry="); pw.println(entry.entry);
         }
         int N = mSnoozedPackages.size();
         pw.println("  snoozed packages: " + N);
@@ -363,8 +379,7 @@
 
     public float getHighestPinnedHeadsUp() {
         float max = 0;
-        for (String key: mHeadsUpEntries.keySet()) {
-            HeadsUpEntry entry = mHeadsUpEntries.get(key);
+        for (HeadsUpEntry entry: mSortedEntries) {
             if (!entry.entry.row.isInShade()) {
                 max = Math.max(max, entry.entry.row.getActualHeight());
             }
@@ -437,13 +452,14 @@
             earliestRemovaltime = currentTime + mMinimumDisplayTime;
             removeAutoCancelCallbacks();
             mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
+            updateSortOrder(HeadsUpEntry.this);
         }
 
         @Override
         public int compareTo(HeadsUpEntry o) {
-            return postTime < o.postTime ? -1
+            return postTime < o.postTime ? 1
                     : postTime == o.postTime ? 0
-                            : 1;
+                            : -1;
         }
 
         public void removeAutoCancelCallbacks() {
@@ -461,6 +477,16 @@
         }
     }
 
+    /**
+     * Update the sorted heads up order.
+     *
+     * @param headsUpEntry the headsUp that changed
+     */
+    private void updateSortOrder(HeadsUpEntry headsUpEntry) {
+        mSortedEntries.remove(headsUpEntry);
+        mSortedEntries.add(headsUpEntry);
+    }
+
     public static class Clock {
         public long currentTimeMillis() {
             return SystemClock.elapsedRealtime();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 824ba94..f2b971f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.stack;
 
 import android.view.View;
+
 import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.ArrayList;
-import java.util.TreeMap;
+import java.util.TreeSet;
 
 /**
  * A global state to track all input states for the algorithm.
@@ -37,7 +39,7 @@
     private boolean mDark;
     private boolean mHideSensitive;
     private HeadsUpManager mHeadsUpManager;
-    private float mPaddingOffset;
+    private float mStackTranslation;
     private int mLayoutHeight;
     private int mTopPadding;
     private boolean mShadeExpanded;
@@ -128,16 +130,16 @@
         mHeadsUpManager = headsUpManager;
     }
 
-    public TreeMap<String, HeadsUpManager.HeadsUpEntry> getHeadsUpEntries() {
-        return mHeadsUpManager.getEntries();
+    public TreeSet<HeadsUpManager.HeadsUpEntry> getSortedHeadsUpEntries() {
+        return mHeadsUpManager.getSortedEntries();
     }
 
-    public float getPaddingOffset() {
-        return mPaddingOffset;
+    public float getStackTranslation() {
+        return mStackTranslation;
     }
 
-    public void setPaddingOffset(float paddingOffset) {
-        mPaddingOffset = paddingOffset;
+    public void setStackTranslation(float stackTranslation) {
+        mStackTranslation = stackTranslation;
     }
 
     public int getLayoutHeight() {
@@ -148,7 +150,7 @@
         mLayoutHeight = layoutHeight;
     }
 
-    public int getTopPadding() {
+    public float getTopPadding() {
         return mTopPadding;
     }
 
@@ -157,7 +159,13 @@
     }
 
     public int getInnerHeight() {
-        return mLayoutHeight - mTopPadding;
+        return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn();
+    }
+
+    private int getTopHeadsUpPushIn() {
+        ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry();
+        return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight()
+                - topHeadsUpEntry.getMinHeight(): 0;
     }
 
     public boolean isShadeExpanded() {
@@ -175,4 +183,9 @@
     public float getMaxHeadsUpTranslation() {
         return mMaxHeadsUpTranslation;
     }
+
+    public ExpandableNotificationRow getTopHeadsUpEntry() {
+        HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry();
+        return topEntry == null ? null : topEntry.entry.row;
+    }
 }
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 cac1b8a..656f23a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -123,15 +123,15 @@
     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
     private AmbientState mAmbientState = new AmbientState();
     private NotificationGroupManager mGroupManager;
-    private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
-    private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
-    private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
-    private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
-    private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
+    private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
+    private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
+    private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
+    private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
+    private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
+    private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
     private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
-    private ArrayList<AnimationEvent> mAnimationEvents
-            = new ArrayList<AnimationEvent>();
-    private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
+    private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
+    private ArrayList<View> mSwipedOutViews = new ArrayList<>();
     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
     private boolean mAnimationsEnabled;
     private boolean mChangePositionInProgress;
@@ -145,7 +145,6 @@
      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
      */
     private float mOverScrolledBottomPixels;
-
     private OnChildLocationsChangedListener mListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
@@ -173,7 +172,6 @@
      * Was the scroller scrolled to the top when the down motion was observed?
      */
     private boolean mScrolledToTopOnFirstDown;
-
     /**
      * The minimal amount of over scroll which is needed in order to switch to the quick settings
      * when over scrolling on a expanded card.
@@ -187,15 +185,14 @@
     private boolean mRequestViewResizeAnimationOnLayout;
     private boolean mNeedViewResizeAnimation;
     private View mExpandedGroupView;
-
     private boolean mEverythingNeedsAnimation;
+
     /**
      * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
      * This is needed to avoid scrolling too far after the notification was collapsed in the same
      * motion.
      */
     private int mMaxScrollAfterExpand;
-
     private SwipeHelper.LongPressListener mLongPressListener;
 
     /**
@@ -206,8 +203,8 @@
     private ViewGroup mScrollView;
     private boolean mInterceptDelegateEnabled;
     private boolean mDelegateToScrollView;
-    private boolean mDisallowScrollingInThisMotion;
 
+    private boolean mDisallowScrollingInThisMotion;
     private long mGoToFullShadeDelay;
     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
@@ -529,7 +526,7 @@
     private void setPaddingOffset(float paddingOffset) {
         if (paddingOffset != mPaddingOffset) {
             mPaddingOffset = paddingOffset;
-            mAmbientState.setPaddingOffset(paddingOffset);
+            mAmbientState.setStackTranslation(paddingOffset);
             requestChildrenUpdate();
         }
     }
@@ -617,13 +614,29 @@
     public void onBeginDrag(View v) {
         setSwipingInProgress(true);
         mAmbientState.onBeginDrag(v);
-        if (mAnimationsEnabled) {
+        if (mAnimationsEnabled && !isPinnedHeadsUp(v)) {
             mDragAnimPendingChildren.add(v);
             mNeedsAnimation = true;
         }
         requestChildrenUpdate();
     }
 
+    private boolean isPinnedHeadsUp(View v) {
+        if (v instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+            return row.isHeadsUp() && !row.isInShade();
+        }
+        return false;
+    }
+
+    private boolean isHeadsUp(View v) {
+        if (v instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+            return row.isHeadsUp();
+        }
+        return false;
+    }
+
     public void onDragCancelled(View v) {
         setSwipingInProgress(false);
     }
@@ -693,6 +706,10 @@
             if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
                 if (slidingChild instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
+                    if (row.isHeadsUp() && !row.isInShade()
+                            && mHeadsUpManager.getTopEntry().entry.row != row) {
+                        continue;
+                    }
                     return row.getViewAtPosition(touchY - childTop);
                 }
                 return slidingChild;
@@ -1788,6 +1805,10 @@
             }
             mNeedsAnimation = true;
         }
+        if (isHeadsUp(child)) {
+            mAddedHeadsUpChildren.add(child);
+            mChildrenToAddAnimated.remove(child);
+        }
     }
 
     /**
@@ -1826,6 +1847,7 @@
     }
 
     private void generateChildHierarchyEvents() {
+        generateHeadsUpAnimationEvents();
         generateChildRemovalEvents();
         generateChildAdditionEvents();
         generatePositionChangeEvents();
@@ -1839,19 +1861,42 @@
         generateGoToFullShadeEvent();
         generateViewResizeEvent();
         generateGroupExpansionEvent();
-        generateHeadsUpAnimationEvents();
         generateAnimateEverythingEvent();
         mNeedsAnimation = false;
     }
 
     private void generateHeadsUpAnimationEvents() {
         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
-            int type = eventPair.second ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR
-                    : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
-            mAnimationEvents.add(new AnimationEvent(eventPair.first,
-                    type));
+            ExpandableNotificationRow row = eventPair.first;
+            boolean isHeadsUp = eventPair.second;
+            int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
+            boolean onBottom = false;
+            if (!row.isInShade() && !isHeadsUp) {
+                type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+            } else if (mAddedHeadsUpChildren.contains(row)) {
+                if (!row.isInShade() || shouldHunAppearFromBottom(row)) {
+                    // Our custom add animation
+                    type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+                } else {
+                    // Normal add animation
+                    type = AnimationEvent.ANIMATION_TYPE_ADD;
+                }
+                onBottom = row.isInShade();
+            }
+            AnimationEvent event = new AnimationEvent(row, type);
+            event.headsUpFromBottom = onBottom;
+            mAnimationEvents.add(event);
         }
         mHeadsUpChangeAnimations.clear();
+        mAddedHeadsUpChildren.clear();
+    }
+
+    private boolean shouldHunAppearFromBottom(ExpandableNotificationRow row) {
+        StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
+        if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
+            return false;
+        }
+        return true;
     }
 
     private void generateGroupExpansionEvent() {
@@ -2649,10 +2694,19 @@
 
     public void setShadeExpanded(boolean shadeExpanded) {
         mAmbientState.setShadeExpanded(shadeExpanded);
+        mStateAnimator.setShadeExpanded(shadeExpanded);
     }
 
-    public void setMaxHeadsUpTranslation(int maxTranslation) {
-        mAmbientState.setMaxHeadsUpTranslation(maxTranslation);
+    /**
+     * Set the boundary for the bottom heads up position. The heads up will always be above this
+     * position.
+     *
+     * @param height the height of the screen
+     * @param bottomBarHeight the height of the bar on the bottom
+     */
+    public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+        mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
+        mStateAnimator.setHeadsUpAppearHeightBottom(height);
         requestChildrenUpdate();
     }
 
@@ -2820,6 +2874,14 @@
                         .animateY()
                         .animateZ(),
 
+                // ANIMATION_TYPE_HEADS_UP_OTHER
+                new AnimationFilter()
+                        .animateAlpha()
+                        .animateHeight()
+                        .animateTopInset()
+                        .animateY()
+                        .animateZ(),
+
                 // ANIMATION_TYPE_EVERYTHING
                 new AnimationFilter()
                         .animateAlpha()
@@ -2883,6 +2945,9 @@
                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
 
+                // ANIMATION_TYPE_HEADS_UP_OTHER
+                StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
                 // ANIMATION_TYPE_EVERYTHING
                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
         };
@@ -2903,7 +2968,8 @@
         static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
         static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
-        static final int ANIMATION_TYPE_EVERYTHING = 16;
+        static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16;
+        static final int ANIMATION_TYPE_EVERYTHING = 17;
 
         static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
         static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
@@ -2915,6 +2981,7 @@
         final long length;
         View viewAfterChangingView;
         int darkAnimationOriginIndex;
+        boolean headsUpFromBottom;
 
         AnimationEvent(View view, int type) {
             this(view, type, LENGTHS[type]);
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 b0f287f..1a42f45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -29,7 +29,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.TreeMap;
+import java.util.TreeSet;
 
 /**
  * The Algorithm of the {@link com.android.systemui.statusbar.stack
@@ -167,7 +167,7 @@
 
         handleDraggedViews(ambientState, resultState, algorithmState);
         updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
-        updateClipping(resultState, algorithmState);
+        updateClipping(resultState, algorithmState, ambientState);
         updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
         getNotificationChildrenStates(resultState, algorithmState);
     }
@@ -198,7 +198,7 @@
     }
 
     private void updateClipping(StackScrollState resultState,
-            StackScrollAlgorithmState algorithmState) {
+            StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
         float previousNotificationEnd = 0;
         float previousNotificationStart = 0;
         boolean previousNotificationIsSwiped = false;
@@ -239,7 +239,7 @@
                 // otherwise we would clip to a transparent view.
                 previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale;
                 previousNotificationEnd = newNotificationEnd;
-                previousNotificationIsSwiped = child.getTranslationX() != 0;
+                previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child);
             }
         }
     }
@@ -311,7 +311,9 @@
                     StackViewState viewState = resultState.getViewStateForView(
                             nextChild);
                     // The child below the dragged one must be fully visible
-                    viewState.alpha = 1;
+                    if (!isPinnedHeadsUpView(draggedView) || isPinnedHeadsUpView(nextChild)) {
+                        viewState.alpha = 1;
+                    }
                 }
 
                 // Lets set the alpha to the one it currently has, as its currently being dragged
@@ -322,6 +324,14 @@
         }
     }
 
+    private boolean isPinnedHeadsUpView(View view) {
+        if (view instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            return row.isHeadsUp() && !row.isInShade();
+        }
+        return false;
+    }
+
     /**
      * Update the visible children on the state.
      */
@@ -332,16 +342,15 @@
         state.visibleChildren.clear();
         state.visibleChildren.ensureCapacity(childCount);
         int notGoneIndex = 0;
-        TreeMap<String, HeadsUpManager.HeadsUpEntry> headsUpEntries =
-                ambientState.getHeadsUpEntries();
-        for (String key: headsUpEntries.keySet()) {
-            ExpandableView v = headsUpEntries.get(key).entry.row;
+        TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries
+                = ambientState.getSortedHeadsUpEntries();
+        for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) {
+            ExpandableView v = entry.entry.row;
             notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
         }
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
-
                 if (v instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) v;
                     if (row.isHeadsUp()) {
@@ -401,13 +410,17 @@
         // How far in is the element currently transitioning into the bottom stack.
         float yPositionInScrollView = 0.0f;
 
+        // If we have a heads-up higher than the collapsed height we need to add the difference to
+        // the padding of all other elements, i.e push in the top stack slightly.
+        ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry();
+
         int childCount = algorithmState.visibleChildren.size();
         int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             StackViewState childViewState = resultState.getViewStateForView(child);
             childViewState.location = StackViewState.LOCATION_UNKNOWN;
-            int childHeight = getMaxAllowedChildHeight(child);
+            int childHeight = getMaxAllowedChildHeight(child, ambientState);
             float yPositionInScrollViewAfterElement = yPositionInScrollView
                     + childHeight
                     + mPaddingBetweenElements;
@@ -486,45 +499,31 @@
             currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
             yPositionInScrollView = yPositionInScrollViewAfterElement;
 
+            if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
+                    && child != topHeadsUpEntry) {
+                childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize;
+            }
             childViewState.yTranslation += ambientState.getTopPadding()
-                    + ambientState.getPaddingOffset();
-
-            updateHeadsUpStates(resultState, algorithmState, ambientState);
+                    + ambientState.getStackTranslation();
         }
+        updateHeadsUpStates(resultState, algorithmState, ambientState);
     }
 
     private void updateHeadsUpStates(StackScrollState resultState,
             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
-        TreeMap<String, HeadsUpManager.HeadsUpEntry> headsUpEntries =
-                ambientState.getHeadsUpEntries();
-        boolean hasPinnedHeadsUp = false;
-        for (String key: headsUpEntries.keySet()) {
-            ExpandableNotificationRow row = headsUpEntries.get(key).entry.row;
+        TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries = ambientState.getSortedHeadsUpEntries();
+        for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) {
+            ExpandableNotificationRow row = entry.entry.row;
             StackViewState childState = resultState.getViewStateForView(row);
             if (!row.isInShade()) {
-                childState.yTranslation = Math.max(childState.yTranslation, 0);
-                hasPinnedHeadsUp = true;
+                childState.yTranslation = 0;
             }
             childState.height = Math.max(childState.height, row.getHeadsUpHeight());
+
+            // Ensure that the heads up is always visible even when scrolled of from the bottom
             childState.yTranslation = Math.min(childState.yTranslation,
                     ambientState.getMaxHeadsUpTranslation() - childState.height);
         }
-        if (hasPinnedHeadsUp && !ambientState.isShadeExpanded()) {
-            // Let's hide all normal views
-            int childCount = algorithmState.visibleChildren.size();
-            for (int i = 0; i < childCount; i++) {
-                ExpandableView child = algorithmState.visibleChildren.get(i);
-                StackViewState state = resultState.getViewStateForView(child);
-                boolean hideView = true;
-                if (child instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                    hideView = !row.isHeadsUp();
-                }
-                if (hideView) {
-                    state.alpha = 0.0f;
-                }
-            }
-        }
     }
 
     /**
@@ -555,7 +554,7 @@
 
     /**
      * Clamp the yTranslation of the child up such that its end is at lest on the end of the top
-     * stack.get
+     * stack.
      *
      * @param childViewState the view state of the child
      * @param childHeight the height of this child
@@ -566,9 +565,12 @@
                 mCollapsedSize - childHeight);
     }
 
-    private int getMaxAllowedChildHeight(View child) {
+    private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            if (ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
+                return mCollapsedSize;
+            }
             return row.getIntrinsicHeight();
         } else if (child instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) child;
@@ -695,7 +697,7 @@
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             StackViewState childViewState = resultState.getViewStateForView(child);
-            int childHeight = getMaxAllowedChildHeight(child);
+            int childHeight = getMaxAllowedChildHeight(child, ambientState);
             float yPositionInScrollViewAfterElement = yPositionInScrollView
                     + childHeight
                     + mPaddingBetweenElements;
@@ -850,7 +852,7 @@
                                 int oldBottom) {
                             if (mFirstChildWhileExpanding != null) {
                                 mFirstChildMaxHeight = getMaxAllowedChildHeight(
-                                        mFirstChildWhileExpanding);
+                                        mFirstChildWhileExpanding, null);
                             } else {
                                 mFirstChildMaxHeight = 0;
                             }
@@ -858,7 +860,7 @@
                         }
                     });
         } else {
-            mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+            mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 9640b84..b869c27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -21,9 +21,11 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.graphics.Path;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -32,7 +34,6 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Set;
 import java.util.Stack;
 
 /**
@@ -46,7 +47,7 @@
     public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
     public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 280;
-    public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 220;
+    public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
     public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
     public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
     public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
@@ -75,12 +76,15 @@
     private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
 
     private final Interpolator mFastOutSlowInInterpolator;
+    private final Interpolator mHeadsUpAppearInterpolator;
     private final int mGoToFullShadeAppearingTranslation;
     public NotificationStackScrollLayout mHostLayout;
     private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
             new ArrayList<>();
     private ArrayList<View> mNewAddChildren = new ArrayList<>();
-    private Set<Animator> mAnimatorSet = new HashSet<>();
+    private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
+    private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
+    private HashSet<Animator> mAnimatorSet = new HashSet<>();
     private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
     private AnimationFilter mAnimationFilter = new AnimationFilter();
     private long mCurrentLength;
@@ -88,11 +92,12 @@
 
     /** The current index for the last child which was not added in this event set. */
     private int mCurrentLastNotAddedIndex;
-
     private ValueAnimator mTopOverScrollAnimator;
     private ValueAnimator mBottomOverScrollAnimator;
     private ExpandableNotificationRow mChildExpandingView;
     private StackViewState mTmpState = new StackViewState();
+    private int mHeadsUpAppearHeightBottom;
+    private boolean mShadeExpanded;
 
     public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
         mHostLayout = hostLayout;
@@ -101,6 +106,10 @@
         mGoToFullShadeAppearingTranslation =
                 hostLayout.getContext().getResources().getDimensionPixelSize(
                         R.dimen.go_to_full_shade_appearing_translation);
+        Path path = new Path();
+        path.moveTo(0, 0);
+        path.cubicTo(0.8f, 0, 0.8f, 1.2f, 1f, 1f);
+        mHeadsUpAppearInterpolator = new PathInterpolator(path);
     }
 
     public boolean isRunning() {
@@ -122,7 +131,8 @@
             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
 
             StackViewState viewState = finalState.getViewStateForView(child);
-            if (viewState == null || child.getVisibility() == View.GONE) {
+            if (viewState == null || child.getVisibility() == View.GONE
+                    || applyWithoutAnimation(child, viewState, finalState)) {
                 continue;
             }
 
@@ -133,11 +143,35 @@
             // no child has preformed any animation, lets finish
             onAnimationFinished();
         }
+        mHeadsUpAppearChildren.clear();
+        mHeadsUpDisappearChildren.clear();
         mNewEvents.clear();
         mNewAddChildren.clear();
         mChildExpandingView = null;
     }
 
+    /**
+     * Determines if a view should not perform an animation and applies it directly.
+     *
+     * @return true if no animation should be performed
+     */
+    private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
+            StackScrollState finalState) {
+        if (mShadeExpanded) {
+            return false;
+        }
+        if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
+            // A Y translation animation is running
+            return false;
+        }
+        if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
+            // This is a heads up animation
+            return false;
+        }
+        finalState.applyState(child, viewState);
+        return true;
+    }
+
     private int findLastNotAddedIndex(StackScrollState finalState) {
         int childCount = mHostLayout.getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
@@ -619,7 +653,9 @@
 
         ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
                 child.getTranslationY(), newEndValue);
-        animator.setInterpolator(mFastOutSlowInInterpolator);
+        Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
+                mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator;
+        animator.setInterpolator(interpolator);
         long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
         animator.setDuration(newDuration);
         if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
@@ -836,8 +872,17 @@
                 // This item is added, initialize it's properties.
                 StackViewState viewState = finalState.getViewStateForView(changingView);
                 mTmpState.copyFrom(viewState);
-                mTmpState.yTranslation = -mTmpState.height;
+                if (event.headsUpFromBottom) {
+                    mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
+                } else {
+                    mTmpState.yTranslation = -mTmpState.height;
+                }
+                mHeadsUpAppearChildren.add(changingView);
                 finalState.applyState(changingView, mTmpState);
+            } else if (event.animationType == NotificationStackScrollLayout
+                    .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
+                // This item is added, initialize it's properties.
+                mHeadsUpDisappearChildren.add(changingView);
             }
             mNewEvents.add(event);
         }
@@ -903,4 +948,12 @@
             return getChildTag(view, TAG_END_HEIGHT);
         }
     }
+
+    public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+        mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
+    }
+
+    public void setShadeExpanded(boolean shadeExpanded) {
+        mShadeExpanded = shadeExpanded;
+    }
 }