Integrate Heads-up notifications into the shade

Change-Id: I4ca0fb4e76e7c974490538c168da0564fe97e0ae
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
new file mode 100644
index 0000000..386be7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 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.phone;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.systemui.Gefingerpoken;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
+/**
+ * A Helper class to handle touches on the heads-up views
+ */
+public class HeadsUpTouchHelper implements Gefingerpoken {
+
+    private HeadsUpManager mHeadsUpManager;
+    private NotificationStackScrollLayout mStackScroller;
+    private int mTrackingPointer;
+    private float mTouchSlop;
+    private float mInitialTouchX;
+    private float mInitialTouchY;
+    private boolean mMotionOnHeadsUpView;
+    private boolean mTrackingHeadsUp;
+    private boolean mCollapseSnoozes;
+    private NotificationPanelView mPanel;
+    private ExpandableNotificationRow mPickedChild;
+
+    public boolean isTrackingHeadsUp() {
+        return mTrackingHeadsUp;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (!mMotionOnHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+            return false;
+        }
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                setTrackingHeadsUp(false);
+                ExpandableView child = mStackScroller.getChildAtPosition(x, y);
+                mMotionOnHeadsUpView = false;
+                if (child instanceof ExpandableNotificationRow) {
+                    mPickedChild = (ExpandableNotificationRow) child;
+                    mMotionOnHeadsUpView = mPickedChild.isHeadsUp() && !mPickedChild.isInShade();
+                }
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialTouchX = event.getX(newIndex);
+                    mInitialTouchY = event.getY(newIndex);
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                final float h = y - mInitialTouchY;
+                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
+                    setTrackingHeadsUp(true);
+                    mCollapseSnoozes = h < 0;
+                    mInitialTouchX = x;
+                    mInitialTouchY = y;
+                    mHeadsUpManager.releaseAllToShade();
+                    int expandedHeight = mPickedChild.getActualHeight();
+                    mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight);
+                    return true;
+                }
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                if (mPickedChild != null && mMotionOnHeadsUpView) {
+                    if (mHeadsUpManager.shouldSwallowClick(
+                            mPickedChild.getStatusBarNotification().getKey())) {
+                        endMotion();
+                        return true;
+                    }
+                }
+                endMotion();
+                break;
+        }
+        return false;
+    }
+
+    private void setTrackingHeadsUp(boolean tracking) {
+        mTrackingHeadsUp = tracking;
+        mHeadsUpManager.setTrackingHeadsUp(tracking);
+        mPanel.setTrackingHeadsUp(tracking);
+    }
+
+    public void notifyFling(boolean collapse) {
+        if (collapse && mCollapseSnoozes) {
+            mHeadsUpManager.snooze();
+        }
+        mCollapseSnoozes = false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!mTrackingHeadsUp) {
+            return false;
+        }
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                endMotion();
+                setTrackingHeadsUp(false);
+                break;
+        }
+        return true;
+    }
+
+    private void endMotion() {
+        mTrackingPointer = -1;
+        mPickedChild = null;
+        mMotionOnHeadsUpView = false;
+    }
+
+    public ExpandableView getPickedChild() {
+        return mPickedChild;
+    }
+
+    public void bind(HeadsUpManager headsUpManager, NotificationStackScrollLayout stackScroller,
+            NotificationPanelView notificationPanelView) {
+        mHeadsUpManager = headsUpManager;
+        mStackScroller = stackScroller;
+        mPanel = notificationPanelView;
+        Context context = stackScroller.getContext();
+        final ViewConfiguration configuration = ViewConfiguration.get(context);
+        mTouchSlop = configuration.getScaledTouchSlop();
+    }
+}
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 02b6c19..6fe609e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,6 +32,7 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -39,8 +40,8 @@
 import android.widget.TextView;
 
 import com.android.keyguard.KeyguardStatusView;
-import com.android.systemui.EventLogTags;
 import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSContainer;
 import com.android.systemui.qs.QSPanel;
@@ -48,7 +49,9 @@
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -56,7 +59,8 @@
 public class NotificationPanelView extends PanelView implements
         ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
         View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
-        KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener {
+        KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
+        HeadsUpManager.OnHeadsUpChangedListener {
 
     private static final boolean DEBUG = false;
 
@@ -81,7 +85,7 @@
     private TextView mClockView;
     private View mReserveNotificationSpace;
     private View mQsNavbarScrim;
-    private View mNotificationContainerParent;
+    private NotificationsQuickSettingsContainer mNotificationContainerParent;
     private NotificationStackScrollLayout mNotificationStackScroller;
     private int mNotificationTopPadding;
     private boolean mAnimateNextTopPaddingChange;
@@ -177,6 +181,16 @@
 
     private float mKeyguardStatusBarAnimateAlpha = 1f;
     private int mOldLayoutDirection;
+    private HeadsUpTouchHelper mHeadsUpTouchHelper = new HeadsUpTouchHelper();
+    private boolean mPinnedHeadsUpExist;
+    private boolean mExpansionIsFromHeadsUp;
+    private int mBottomBarHeight;
+    private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
+        @Override
+        public void run() {
+            notifyBarPanelExpansionChanged();
+        }
+    };
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -201,7 +215,8 @@
         mScrollView.setListener(this);
         mScrollView.setFocusable(false);
         mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
-        mNotificationContainerParent = findViewById(R.id.notification_container_parent);
+        mNotificationContainerParent = (NotificationsQuickSettingsContainer)
+                findViewById(R.id.notification_container_parent);
         mNotificationStackScroller = (NotificationStackScrollLayout)
                 findViewById(R.id.notification_stack_scroller);
         mNotificationStackScroller.setOnHeightChangedListener(this);
@@ -317,6 +332,7 @@
         if (mQsSizeChangeAnimator == null) {
             mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
         }
+        updateMaxHeadsUpTranslation();
     }
 
     @Override
@@ -493,22 +509,42 @@
     }
 
     @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);
+    }
+
+    @Override
     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
         if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
             event.getText().add(getKeyguardOrLockScreenString());
             mLastAnnouncementWasQuickSettings = false;
             return true;
         }
-
         return super.dispatchPopulateAccessibilityEventInternal(event);
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
+    public boolean
+    onInterceptTouchEvent(MotionEvent event) {
         if (mBlockTouches) {
             return false;
         }
         initDownStates(event);
+        if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+            mExpansionIsFromHeadsUp = true;
+            return true;
+        }
+        if (onQsIntercept(event)) {
+            return true;
+        }
+        return super.onInterceptTouchEvent(event);
+    }
+
+    private boolean onQsIntercept(MotionEvent event) {
         int pointerIndex = event.findPointerIndex(mTrackingPointer);
         if (pointerIndex < 0) {
             pointerIndex = 0;
@@ -583,7 +619,7 @@
                 mIntercepting = false;
                 break;
         }
-        return super.onInterceptTouchEvent(event);
+        return false;
     }
 
     @Override
@@ -652,6 +688,15 @@
         if (mOnlyAffordanceInThisMotion) {
             return true;
         }
+        mHeadsUpTouchHelper.onTouchEvent(event);
+        if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQSTouch(event)) {
+            return true;
+        }
+        super.onTouchEvent(event);
+        return true;
+    }
+
+    private boolean handleQSTouch(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
                 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
                 && mQsExpansionEnabled) {
@@ -691,8 +736,7 @@
             // earlier so the state is already up to date when dragging down.
             setListening(true);
         }
-        super.onTouchEvent(event);
-        return true;
+        return false;
     }
 
     private boolean isInQsArea(float x, float y) {
@@ -834,13 +878,13 @@
         setQsExpansion(mQsExpansionHeight);
         flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
                 new Runnable() {
-            @Override
-            public void run() {
-                mStackScrollerOverscrolling = false;
-                mQsExpansionFromOverscroll = false;
-                updateQsState();
-            }
-        });
+                    @Override
+                    public void run() {
+                        mStackScrollerOverscrolling = false;
+                        mQsExpansionFromOverscroll = false;
+                        updateQsState();
+                    }
+                });
     }
 
     private void onQsExpansionStarted() {
@@ -869,6 +913,7 @@
             mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
             mStatusBar.setQsExpanded(expanded);
             mQsPanel.setExpanded(expanded);
+            mNotificationContainerParent.setQsExpanded(expanded);
         }
     }
 
@@ -1056,7 +1101,7 @@
             mKeyguardBottomArea.animate()
                     .alpha(0f)
                     .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
-                    .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
+                    .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2)
                     .setInterpolator(PhoneStatusBar.ALPHA_OUT)
                     .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
                     .start();
@@ -1402,6 +1447,8 @@
         updateHeader();
         updateUnlockIcon();
         updateNotificationTranslucency();
+        mHeadsUpManager.setIsExpanded(!isShadeCollapsed());
+        mNotificationStackScroller.setShadeExpanded(!isShadeCollapsed());
         if (DEBUG) {
             invalidate();
         }
@@ -1544,7 +1591,14 @@
                 return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
             }
         }
-        return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
+        float paddingOffset = mNotificationStackScroller.getPaddingOffset();
+        float translation = paddingOffset / HEADER_RUBBERBAND_FACTOR;
+        if (mHeadsUpManager.hasPinnedHeadsUp() || mExpansionIsFromHeadsUp) {
+            translation = mNotificationStackScroller.getTopPadding()
+                    + mNotificationStackScroller.getPaddingOffset()
+                    - mNotificationTopPadding - mQsMinExpansionHeight;
+        }
+        return Math.min(0, translation);
     }
 
     /**
@@ -1605,8 +1659,10 @@
     protected void onExpandingFinished() {
         super.onExpandingFinished();
         mNotificationStackScroller.onExpansionStopped();
+        mHeadsUpManager.onExpandingFinished();
         mIsExpanding = false;
         mScrollYOverride = -1;
+        // TODO: look into whether this is still correct
         if (mExpandedHeight == 0f) {
             setListening(false);
         } else {
@@ -1614,6 +1670,8 @@
         }
         mQsExpandImmediate = false;
         mTwoFingerQsExpandPossible = false;
+        mExpansionIsFromHeadsUp = false;
+        mNotificationStackScroller.setTrackingHeadsUp(mHeadsUpTouchHelper.isTrackingHeadsUp());
     }
 
     private void setListening(boolean listening) {
@@ -1709,6 +1767,17 @@
     }
 
     @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mBottomBarHeight = insets.getSystemWindowInsetBottom();
+        updateMaxHeadsUpTranslation();
+        return insets;
+    }
+
+    private void updateMaxHeadsUpTranslation() {
+        mNotificationStackScroller.setMaxHeadsUpTranslation(getHeight() - mBottomBarHeight);
+    }
+
+    @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
         if (layoutDirection != mOldLayoutDirection) {
             mAfforanceHelper.onRtlPropertiesChanged();
@@ -2066,4 +2135,44 @@
                     mNotificationStackScroller.getTopPadding(), p);
         }
     }
+
+    @Override
+    public void OnPinnedHeadsUpExistChanged(final boolean exist, boolean changeImmediatly) {
+        if (exist != mPinnedHeadsUpExist) {
+            mPinnedHeadsUpExist = exist;
+            if (!exist) {
+                mNotificationStackScroller.performOnAnimationFinished(
+                        mHeadsUpExistenceChangedRunnable);
+            } else {
+                mHeadsUpExistenceChangedRunnable.run();
+            }
+        }
+    }
+
+    @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
+    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+        super.setHeadsUpManager(headsUpManager);
+        mHeadsUpTouchHelper.bind(headsUpManager, mNotificationStackScroller, this);
+    }
+
+    public void setTrackingHeadsUp(boolean tracking) {
+        if (tracking) {
+            // otherwise we update the state when the expansion is finished
+            mNotificationStackScroller.setTrackingHeadsUp(true);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index a03c297..cbb71c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -37,6 +37,7 @@
     private View mStackScroller;
     private View mKeyguardStatusBar;
     private boolean mInflated;
+    private boolean mQsExpanded;
 
     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -64,26 +65,29 @@
         boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
         boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
 
+        View stackQsTop = mQsExpanded ? mStackScroller : mScrollView;
+        View stackQsBottom = !mQsExpanded ? mStackScroller : mScrollView;
         // Invert the order of the scroll view and user switcher such that the notifications receive
         // touches first but the panel gets drawn above.
         if (child == mScrollView) {
-            return super.drawChild(canvas, mStackScroller, drawingTime);
-        } else if (child == mStackScroller) {
-            return super.drawChild(canvas,
-                    userSwitcherVisible && statusBarVisible ? mUserSwitcher
+            return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher
                     : statusBarVisible ? mKeyguardStatusBar
                     : userSwitcherVisible ? mUserSwitcher
-                    : mScrollView,
+                    : stackQsBottom, drawingTime);
+        } else if (child == mStackScroller) {
+            return super.drawChild(canvas,
+                    userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
+                    : statusBarVisible || userSwitcherVisible ? stackQsBottom
+                    : stackQsTop,
                     drawingTime);
         } else if (child == mUserSwitcher) {
             return super.drawChild(canvas,
-                    userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
-                    : mScrollView,
+                    userSwitcherVisible && statusBarVisible ? stackQsBottom
+                    : stackQsTop,
                     drawingTime);
         } else if (child == mKeyguardStatusBar) {
             return super.drawChild(canvas,
-                    userSwitcherVisible && statusBarVisible ? mScrollView
-                    : mScrollView,
+                    stackQsTop,
                     drawingTime);
         }else {
             return super.drawChild(canvas, child, drawingTime);
@@ -97,4 +101,11 @@
             mInflated = true;
         }
     }
+
+    public void setQsExpanded(boolean expanded) {
+        if (mQsExpanded != expanded) {
+            mQsExpanded = expanded;
+            invalidate();
+        }
+    }
 }
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 c6e1be9..240438a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -157,8 +157,7 @@
         if (DEBUG) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName());
         mPanelExpandedFractionSum = 0f;
         for (PanelView pv : mPanels) {
-            boolean visible = pv.getExpandedHeight() > 0;
-            pv.setVisibility(visible ? View.VISIBLE : View.GONE);
+            pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
             // adjust any other panels that may be partially visible
             if (expanded) {
                 if (mState == STATE_CLOSED) {
@@ -167,7 +166,7 @@
                 }
                 fullyClosed = false;
                 final float thisFrac = pv.getExpandedFraction();
-                mPanelExpandedFractionSum += (visible ? thisFrac : 0);
+                mPanelExpandedFractionSum += thisFrac;
                 if (DEBUG) LOG("panelExpansionChanged:  -> %s: f=%.1f", pv.getName(), thisFrac);
                 if (panel == pv) {
                     if (thisFrac == 1f) fullyOpenedPanel = panel;
@@ -196,7 +195,6 @@
             } else {
                 pv.resetViews();
                 pv.setExpandedFraction(0); // just in case
-                pv.setVisibility(View.GONE);
                 pv.cancelPeek();
             }
         }
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 4bbf690..1b89b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -38,6 +38,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -45,6 +46,7 @@
 public abstract class PanelView extends FrameLayout {
     public static final boolean DEBUG = PanelBar.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
+    protected HeadsUpManager mHeadsUpManager;
 
     private final void logf(String fmt, Object... args) {
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -238,10 +240,7 @@
 
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                mInitialOffsetOnTouch = mExpandedHeight;
-                mTouchSlopExceeded = false;
+                startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                 mJustPeeked = false;
                 mPanelClosedOnDown = mExpandedHeight == 0.0f;
                 mHasLayoutedSinceDown = false;
@@ -274,9 +273,7 @@
                     final float newY = event.getY(newIndex);
                     final float newX = event.getX(newIndex);
                     mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialOffsetOnTouch = mExpandedHeight;
-                    mInitialTouchY = newY;
-                    mInitialTouchX = newX;
+                    startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
                 }
                 break;
             case MotionEvent.ACTION_POINTER_DOWN:
@@ -297,9 +294,7 @@
                     mTouchSlopExceeded = true;
                     if (waitForTouchSlop && !mTracking) {
                         if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
-                            mInitialOffsetOnTouch = mExpandedHeight;
-                            mInitialTouchX = x;
-                            mInitialTouchY = y;
+                            startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                             h = 0;
                         }
                         cancelHeightAnimator();
@@ -334,6 +329,17 @@
         return !waitForTouchSlop || mTracking;
     }
 
+    protected void startExpandMotion(float newX, float newY, boolean startTracking,
+            float expandedHeight) {
+        mInitialOffsetOnTouch = expandedHeight;
+        mInitialTouchY = newY;
+        mInitialTouchX = newX;
+        if (startTracking) {
+            mTouchSlopExceeded = true;
+            onTrackingStarted();
+        }
+    }
+
     private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
         mTrackingPointer = -1;
         if ((mTracking && mTouchSlopExceeded)
@@ -474,12 +480,7 @@
                 if (scrolledToBottom || mTouchStartedInEmptyArea) {
                     if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
                         cancelHeightAnimator();
-                        mInitialOffsetOnTouch = mExpandedHeight;
-                        mInitialTouchY = y;
-                        mInitialTouchX = x;
-                        mTracking = true;
-                        mTouchSlopExceeded = true;
-                        onTrackingStarted();
+                        startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
                         return true;
                     }
                 }
@@ -564,7 +565,10 @@
     protected void fling(float vel, boolean expand) {
         cancelPeek();
         float target = expand ? getMaxPanelHeight() : 0.0f;
+        flingToHeight(vel, expand, target);
+    }
 
+    protected void flingToHeight(float vel, boolean expand, float target) {
         // Hack to make the expand transition look nice when clear all button is visible - we make
         // the animation only to the last notification, and then jump to the maximum panel height so
         // clear all just fades in and the decelerating motion is towards the last notification.
@@ -644,7 +648,7 @@
         mHasLayoutedSinceDown = true;
         if (mUpdateFlingOnLayout) {
             abortAnimations();
-            fling(mUpdateFlingVelocity, true);
+            fling(mUpdateFlingVelocity, true /* expands */);
             mUpdateFlingOnLayout = false;
         }
     }
@@ -805,7 +809,7 @@
         if (mExpanding) {
             notifyExpandingFinished();
         }
-        setVisibility(VISIBLE);
+        notifyBarPanelExpansionChanged();
 
         // Wait for window manager to pickup the change, so we know the maximum height of the panel
         // then.
@@ -941,9 +945,9 @@
         return animator;
     }
 
-    private void notifyBarPanelExpansionChanged() {
+    protected void notifyBarPanelExpansionChanged() {
         mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending
-                || mPeekAnimator != null);
+                || mPeekAnimator != null || mInstantExpanding || mHeadsUpManager.hasPinnedHeadsUp());
     }
 
     /**
@@ -1014,4 +1018,8 @@
      * @return the height of the clear all button, in pixels
      */
     protected abstract int getClearAllHeight();
+
+    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 b0d6178..74a26a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -17,19 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.app.StatusBarManager.windowStateToString;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.NonNull;
@@ -92,7 +79,6 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewStub;
 import android.view.WindowManager;
@@ -137,7 +123,6 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.SpeedBumpView;
-import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.AccessibilityController;
@@ -147,7 +132,7 @@
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HotspotControllerImpl;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -172,11 +157,26 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
+
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.app.StatusBarManager.windowStateToString;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
 
 public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
-        DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener {
+        DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, HeadsUpManager.OnHeadsUpChangedListener {
     static final String TAG = "PhoneStatusBar";
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
     public static final boolean SPEW = false;
@@ -360,11 +360,7 @@
             if (wasUsing != mUseHeadsUp) {
                 if (!mUseHeadsUp) {
                     Log.d(TAG, "dismissing any existing heads up notification on disable event");
-                    setHeadsUpVisibility(false);
-                    mHeadsUpNotificationView.releaseImmediately();
-                    removeHeadsUpView();
-                } else {
-                    addHeadsUpView();
+                    mHeadsUpManager.releaseAllImmediately();
                 }
             }
         }
@@ -528,6 +524,8 @@
     };
     private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
             = new HashMap<>();
+    private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
+    private RankingMap mLatestRankingMap;
 
     @Override
     public void start() {
@@ -598,7 +596,8 @@
                     }
                 }
                 return mStatusBarWindow.onTouchEvent(event);
-            }});
+            }
+        });
 
         mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
         mStatusBarView.setBar(this);
@@ -615,12 +614,13 @@
             mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
                     R.color.notification_panel_solid_background)));
         }
-        if (ENABLE_HEADS_UP) {
-            mHeadsUpNotificationView =
-                    (HeadsUpNotificationView) View.inflate(context, R.layout.heads_up, null);
-            mHeadsUpNotificationView.setVisibility(View.GONE);
-            mHeadsUpNotificationView.setBar(this);
-        }
+
+        mHeadsUpManager = new HeadsUpManager(context);
+        mHeadsUpManager.setBar(this);
+        mHeadsUpManager.addListener(this);
+        mHeadsUpManager.addListener(mNotificationPanel);
+        mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
+
         if (MULTIUSER_DEBUG) {
             mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
                     R.id.header_debug_info);
@@ -667,6 +667,7 @@
         mStackScroller.setLongPressListener(getNotificationLongClicker());
         mStackScroller.setPhoneStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
+        mStackScroller.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setOnGroupChangeListener(mStackScroller);
 
         mKeyguardIconOverflowContainer =
@@ -1084,32 +1085,6 @@
         return lp;
     }
 
-    private void addHeadsUpView() {
-        int headsUpHeight = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.heads_up_window_height);
-        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                LayoutParams.MATCH_PARENT, headsUpHeight,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
-                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
-                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                PixelFormat.TRANSLUCENT);
-        lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-        lp.gravity = Gravity.TOP;
-        lp.setTitle("Heads Up");
-        lp.packageName = mContext.getPackageName();
-        lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp;
-
-        mWindowManager.addView(mHeadsUpNotificationView, lp);
-    }
-
-    private void removeHeadsUpView() {
-        mWindowManager.removeView(mHeadsUpNotificationView);
-    }
-
     public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
         mIconController.addSystemIcon(slot, index, viewIndex, icon);
     }
@@ -1131,30 +1106,15 @@
     public void addNotification(StatusBarNotification notification, RankingMap ranking,
             Entry oldEntry) {
         if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
-        if (mUseHeadsUp && shouldInterrupt(notification)) {
-            if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
-            Entry interruptionCandidate = oldEntry;
-            if (interruptionCandidate == null) {
-                final StatusBarIconView iconView = createIcon(notification);
-                if (iconView == null) {
-                    return;
-                }
-                interruptionCandidate = new Entry(notification, iconView);
-            }
-            ViewGroup holder = mHeadsUpNotificationView.getHolder();
-            if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
-                // 1. Populate mHeadsUpNotificationView
-                mHeadsUpNotificationView.showNotification(interruptionCandidate);
+        boolean isHeadsUp = mUseHeadsUp && shouldInterrupt(notification);
 
-                // do not show the notification in the shade, yet.
-                return;
-            }
-        }
-
-        Entry shadeEntry = createNotificationViews(notification);
+        Entry shadeEntry = createNotificationViews(notification, isHeadsUp);
         if (shadeEntry == null) {
             return;
         }
+        if (isHeadsUp) {
+            mHeadsUpManager.showNotification(shadeEntry);
+        }
 
         if (notification.getNotification().fullScreenIntent != null) {
             // Stop screensaver if the notification has a full-screen intent.
@@ -1175,18 +1135,6 @@
         setAreThereNotifications();
     }
 
-    public void displayNotificationFromHeadsUp(Entry shadeEntry) {
-
-        // The notification comes from the headsup, let's inflate the normal layout again
-        inflateViews(shadeEntry, mStackScroller);
-        shadeEntry.setInterruption();
-        shadeEntry.row.setHeadsUp(false);
-
-        addNotificationViews(shadeEntry, null);
-        // Recalculate the position of the sliding windows and the titles.
-        setAreThereNotifications();
-    }
-
     @Override
     protected void updateNotificationRanking(RankingMap ranking) {
         mNotificationData.updateRanking(ranking);
@@ -1195,10 +1143,15 @@
 
     @Override
     public void removeNotification(String key, RankingMap ranking) {
-        if (ENABLE_HEADS_UP) {
-            mHeadsUpNotificationView.removeNotification(key);
+        boolean defferRemoval = false;
+        if (mHeadsUpManager.isHeadsUp(key)) {
+            defferRemoval = !mHeadsUpManager.removeNotification(key);
         }
-
+        if (defferRemoval) {
+            mLatestRankingMap = ranking;
+            mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+            return;
+        }
         StatusBarNotification old = removeNotificationViews(key, ranking);
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
@@ -1870,6 +1823,38 @@
         logStateToEventlog();
     }
 
+    @Override
+    public void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly) {
+        if (exist) {
+            mStatusBarWindowManager.setHeadsUpShowing(true);
+        } else {
+            Runnable endRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+                        mStatusBarWindowManager.setHeadsUpShowing(false);
+                    }
+                }
+            };
+            if (changeImmediatly) {
+                endRunnable.run();
+            } else {
+                mStackScroller.performOnAnimationFinished(endRunnable);
+            }
+        }
+    }
+
+    @Override
+    public void OnHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
+        if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+            removeNotification(entry.key, mLatestRankingMap);
+            mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+            if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+                mLatestRankingMap = null;
+            }
+        }
+    }
+
     /**
      * All changes to the status bar and notifications funnel through here and are batched.
      */
@@ -1886,15 +1871,6 @@
                 case MSG_CLOSE_PANELS:
                     animateCollapsePanels();
                     break;
-                case MSG_SHOW_HEADS_UP:
-                    setHeadsUpVisibility(true);
-                    break;
-                case MSG_ESCALATE_HEADS_UP:
-                    escalateHeadsUp();
-                case MSG_HIDE_HEADS_UP:
-                    mHeadsUpNotificationView.releaseImmediately();
-                    setHeadsUpVisibility(false);
-                    break;
                 case MSG_LAUNCH_TRANSITION_TIMEOUT:
                     onLaunchTransitionTimeout();
                     break;
@@ -1903,44 +1879,16 @@
     }
 
     @Override
-    public void scheduleHeadsUpDecay(long delay) {
-        mHandler.removeMessages(MSG_HIDE_HEADS_UP);
-        if (mHeadsUpNotificationView.isClearable()) {
-            mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, delay);
-        }
-    }
-
-    @Override
-    public void scheduleHeadsUpOpen() {
-        mHandler.removeMessages(MSG_HIDE_HEADS_UP);
-        mHandler.removeMessages(MSG_SHOW_HEADS_UP);
-        mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
-    }
-
-    @Override
-    public void scheduleHeadsUpClose() {
-        mHandler.removeMessages(MSG_HIDE_HEADS_UP);
-        if (mHeadsUpNotificationView.getVisibility() != View.GONE) {
-            mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
-        }
-    }
-
-    @Override
-    public void scheduleHeadsUpEscalation() {
-        mHandler.removeMessages(MSG_HIDE_HEADS_UP);
-        mHandler.removeMessages(MSG_ESCALATE_HEADS_UP);
-        mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
-    }
-
-    /**  if the interrupting notification had a fullscreen intent, fire it now.  */
-    private void escalateHeadsUp() {
-        if (mHeadsUpNotificationView.getEntry() != null) {
-            final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification;
-            mHeadsUpNotificationView.releaseImmediately();
+    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;
             final Notification notification = sbn.getNotification();
             if (notification.fullScreenIntent != null) {
-                if (DEBUG)
+                if (DEBUG) {
                     Log.d(TAG, "converting a heads up to fullScreen");
+                }
                 try {
                     EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
                             sbn.getKey());
@@ -1949,6 +1897,7 @@
                 }
             }
         }
+        mHeadsUpManager.releaseAllImmediately();
     }
 
     boolean panelsEnabled() {
@@ -2081,10 +2030,6 @@
         // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
         mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/);
 
-        // reset things to their proper state
-        mStackScroller.setVisibility(View.VISIBLE);
-        mNotificationPanel.setVisibility(View.GONE);
-
         mNotificationPanel.closeQs();
 
         mExpandedVisible = false;
@@ -2478,8 +2423,6 @@
         pw.println(Settings.Global.zenModeToString(mZenMode));
         pw.print("  mUseHeadsUp=");
         pw.println(mUseHeadsUp);
-        pw.print("  interrupting package: ");
-        pw.println(hunStateToString(mHeadsUpNotificationView.getEntry()));
         dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
         if (mNavigationBarView != null) {
             pw.print("  mNavigationBarWindowState=");
@@ -2571,10 +2514,10 @@
         if (mSecurityController != null) {
             mSecurityController.dump(fd, pw, args);
         }
-        if (mHeadsUpNotificationView != null) {
-            mHeadsUpNotificationView.dump(fd, pw, args);
+        if (mHeadsUpManager != null) {
+            mHeadsUpManager.dump(fd, pw, args);
         } else {
-            pw.println("  mHeadsUpNotificationView: null");
+            pw.println("  mHeadsUpManager: null");
         }
 
         pw.println("SharedPreferences:");
@@ -2672,7 +2615,7 @@
             else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 mScreenOn = false;
                 notifyNavigationBarScreenOn(false);
-                notifyHeadsUpScreenOn(false);
+                notifyHeadsUpScreenOff();
                 finishBarAnimations();
                 resetUserExpandedStates();
             }
@@ -2764,15 +2707,6 @@
                 mUserSetupObserver, mCurrentUserId);
     }
 
-    private void setHeadsUpVisibility(boolean vis) {
-        if (!ENABLE_HEADS_UP) return;
-        if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window");
-        EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_STATUS,
-                vis ? mHeadsUpNotificationView.getKey() : "",
-                vis ? 1 : 0);
-        mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE);
-    }
-
     /**
      * Reload some of our resources when the configuration changes.
      *
@@ -2791,9 +2725,6 @@
         if (mNotificationPanel != null) {
             mNotificationPanel.updateResources();
         }
-        if (mHeadsUpNotificationView != null) {
-            mHeadsUpNotificationView.updateResources();
-        }
         if (mBrightnessMirrorController != null) {
             mBrightnessMirrorController.updateResources();
         }
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 63bbf97..6e0bf8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -130,7 +130,7 @@
 
     private void applyHeight(State state) {
         boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded
-                || state.keyguardFadingAway || state.bouncerShowing;
+                || state.keyguardFadingAway || state.bouncerShowing || state.headsUpShowing;
         if (expanded) {
             mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT;
         } else {
@@ -218,6 +218,11 @@
         apply(mCurrentState);
     }
 
+    public void setHeadsUpShowing(boolean showing) {
+        mCurrentState.headsUpShowing = showing;
+        apply(mCurrentState);
+    }
+
     /**
      * @param state The {@link StatusBarState} of the status bar.
      */
@@ -235,6 +240,7 @@
         boolean bouncerShowing;
         boolean keyguardFadingAway;
         boolean qsExpanded;
+        boolean headsUpShowing;
 
         /**
          * The {@link BaseStatusBar} state from the status bar.