Changed the appearance of notification bundles

Children now show up as one line notifications even
if the summary is not expanded. The childrenContainer
shows a summary if there are some which don't fit
in there currently.

Bug: 24866646
Change-Id: I0cfae9342722c9f8941f51704618190cfe4e76b4
diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml
new file mode 100644
index 0000000..9e64d2c
--- /dev/null
+++ b/packages/SystemUI/res/layout/hybrid_notification.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<com.android.systemui.statusbar.notification.HybridNotificationView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/notification_max_height">
+    <TextView
+        android:id="@+id/notification_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:singleLine="true"
+        android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+        android:paddingEnd="4dp"
+    />
+    <TextView
+        android:id="@+id/notification_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textAppearance="@*android:style/TextAppearance.Material.Notification"
+        android:singleLine="true"
+        />
+</com.android.systemui.statusbar.notification.HybridNotificationView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9fd82ed..c413524 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -36,6 +36,18 @@
     <!-- The font size for the clock -->
     <dimen name="status_bar_clock_size">14sp</dimen>
 
+    <!-- The margin on the start of the content view -->
+    <dimen name="notification_content_margin_start">16dp</dimen>
+
+    <!-- The maximum size of the title when in single line mode -->
+    <dimen name="notification_maximum_title_length">150sp</dimen>
+
+    <!-- The margin on the end of the content view -->
+    <dimen name="notification_content_margin_end">8dp</dimen>
+
+    <!-- Height of a single line notification in the status bar -->
+    <dimen name="notification_single_line_height">32sp</dimen>
+
     <!-- Height of a small notification in the status bar -->
     <dimen name="notification_min_height">64dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 844bab3..d5f9557e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1026,6 +1026,9 @@
     <!-- VolumeUI restoration notification: text -->
     <string name="volumeui_notification_text">Touch to restore the original.</string>
 
+    <!-- Describes the way 2 names are concatenated. An example would be ", " to produce "Peter Muller, Paul Curry". Please also include a space here if it's appropriate in the language and if it's a RTL language include it on the left. [CHAR LIMIT=3] -->
+    <string name="group_summary_concadenation">, </string>
+
     <!-- Toast shown when user unlocks screen and managed profile activity is in the foreground -->
     <string name="managed_profile_foreground_toast">You\'re using your work profile</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index ec22b9f..1e038c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1929,27 +1929,29 @@
         for (int i = 0; i < N; i++) {
             NotificationData.Entry entry = activeNotifications.get(i);
             if (onKeyguard) {
-                entry.row.setExpansionDisabled(true);
+                entry.row.setOnKeyguard(true);
             } else {
-                entry.row.setExpansionDisabled(false);
-                if (!entry.row.isUserLocked()) {
-                    boolean top = (i == 0);
-                    entry.row.setSystemExpanded(top);
-                }
+                entry.row.setOnKeyguard(false);
+                boolean top = (i == 0);
+                entry.row.setSystemExpanded(top);
             }
-            boolean isInvisibleChild = !mGroupManager.isVisible(entry.notification);
+            boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
+            boolean childWithVisibleSummary = childNotification
+                    && mGroupManager.getGroupSummary(entry.notification).getVisibility()
+                    == View.VISIBLE;
             boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
             if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
                     (onKeyguard && (visibleNotifications >= maxKeyguardNotifications
-                            || !showOnKeyguard || isInvisibleChild))) {
+                            && !childWithVisibleSummary
+                            || !showOnKeyguard))) {
                 entry.row.setVisibility(View.GONE);
-                if (onKeyguard && showOnKeyguard && !isInvisibleChild) {
+                if (onKeyguard && showOnKeyguard && !childNotification) {
                     mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
                 }
             } else {
                 boolean wasGone = entry.row.getVisibility() == View.GONE;
                 entry.row.setVisibility(View.VISIBLE);
-                if (!isInvisibleChild) {
+                if (!childNotification) {
                     if (wasGone) {
                         // notify the scroller of a child addition
                         mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index b8df139..7078f33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -70,9 +70,9 @@
     private boolean mIsSystemExpanded;
 
     /**
-     * Whether the notification expansion is disabled. This is the case on Keyguard.
+     * Whether the notification is on the keyguard and the expansion is disabled.
      */
-    private boolean mExpansionDisabled;
+    private boolean mOnKeyguard;
 
     private NotificationContentView mPublicLayout;
     private NotificationContentView mPrivateLayout;
@@ -162,6 +162,7 @@
 
     private void setStatusBarNotification(StatusBarNotification statusBarNotification) {
         mStatusBarNotification = statusBarNotification;
+        mPrivateLayout.setStatusBarNotification(statusBarNotification);
         updateVetoButton();
         onChildrenCountChanged();
     }
@@ -185,6 +186,7 @@
 
     public void setGroupManager(NotificationGroupManager groupManager) {
         mGroupManager = groupManager;
+        mPrivateLayout.setGroupManager(groupManager);
     }
 
     public void addChildNotification(ExpandableNotificationRow row) {
@@ -225,6 +227,7 @@
     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
         mChildInGroup = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
         mShowNoBackground = mChildInGroup && hasSameBgColor(parent);
+        mPrivateLayout.setIsChildInGroup(mShowNoBackground);
         updateBackground();
     }
 
@@ -265,34 +268,34 @@
     }
 
     public void getChildrenStates(StackScrollState resultState) {
-        if (mChildrenExpanded) {
+        if (mIsSummaryWithChildren) {
             StackViewState parentState = resultState.getViewStateForView(this);
             mChildrenContainer.getState(resultState, parentState);
         }
     }
 
     public void applyChildrenState(StackScrollState state) {
-        if (mChildrenExpanded) {
+        if (mIsSummaryWithChildren) {
             mChildrenContainer.applyState(state);
         }
     }
 
     public void prepareExpansionChanged(StackScrollState state) {
-        if (mChildrenExpanded) {
+        if (mIsSummaryWithChildren) {
             mChildrenContainer.prepareExpansionChanged(state);
         }
     }
 
     public void startChildAnimation(StackScrollState finalState,
             StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
-        if (mChildrenExpanded) {
+        if (mIsSummaryWithChildren) {
             mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
                     duration);
         }
     }
 
     public ExpandableNotificationRow getViewAtPosition(float y) {
-        if (!mChildrenExpanded) {
+        if (!mIsSummaryWithChildren || !mChildrenExpanded) {
             return this;
         } else {
             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
@@ -416,7 +419,7 @@
         mSensitive = false;
         mShowingPublicInitialized = false;
         mIsSystemExpanded = false;
-        mExpansionDisabled = false;
+        mOnKeyguard = false;
         mPublicLayout.reset(mIsHeadsUp);
         mPrivateLayout.reset(mIsHeadsUp);
         resetHeight();
@@ -580,16 +583,19 @@
             mIsSystemExpanded = expand;
             notifyHeightChanged(false /* needsAnimation */);
             logExpansionEvent(false, wasExpanded);
+            if (mChildrenContainer != null) {
+                mChildrenContainer.updateGroupOverflow();
+            }
         }
     }
 
     /**
-     * @param expansionDisabled whether to prevent notification expansion
+     * @param onKeyguard whether to prevent notification expansion
      */
-    public void setExpansionDisabled(boolean expansionDisabled) {
-        if (expansionDisabled != mExpansionDisabled) {
+    public void setOnKeyguard(boolean onKeyguard) {
+        if (onKeyguard != mOnKeyguard) {
             final boolean wasExpanded = isExpanded();
-            mExpansionDisabled = expansionDisabled;
+            mOnKeyguard = onKeyguard;
             logExpansionEvent(false, wasExpanded);
             if (wasExpanded != isExpanded()) {
                 notifyHeightChanged(false  /* needsAnimation */);
@@ -622,23 +628,22 @@
             return getActualHeight();
         }
         boolean inExpansionState = isExpanded();
-        int maxContentHeight;
         if (mSensitive && mHideSensitiveForIntrinsicHeight) {
             return mRowMinHeight;
+        } else if (mIsSummaryWithChildren && !mOnKeyguard) {
+            return mChildrenContainer.getIntrinsicHeight()
+                    + mNotificationHeader.getHeight();
         } else if (mIsHeadsUp) {
             if (inExpansionState) {
-                maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight);
+                return Math.max(mMaxExpandHeight, mHeadsUpHeight);
             } else {
-                maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight);
+                return Math.max(mRowMinHeight, mHeadsUpHeight);
             }
-        } else if ((!inExpansionState && !mChildrenExpanded)) {
-            maxContentHeight = mRowMinHeight;
-        } else if (mChildrenExpanded) {
-            maxContentHeight = mChildrenContainer.getIntrinsicHeight();
+        } else if (!inExpansionState || (mChildInGroup && !isGroupExpanded())) {
+            return getMinHeight();
         } else {
-            maxContentHeight = getMaxExpandHeight();
+            return getMaxExpandHeight();
         }
-        return maxContentHeight;
     }
 
     private boolean isGroupExpanded() {
@@ -670,8 +675,8 @@
      *
      * @return whether the view state is currently expanded.
      */
-    private boolean isExpanded() {
-        return !mExpansionDisabled
+    public boolean isExpanded() {
+        return !mOnKeyguard
                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
                 || isUserExpanded());
     }
@@ -778,6 +783,9 @@
 
     public void setChildrenExpanded(boolean expanded, boolean animate) {
         mChildrenExpanded = expanded;
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setChildrenExpanded(expanded);
+        }
     }
 
     public void updateNotificationHeader() {
@@ -844,12 +852,20 @@
 
     @Override
     public int getMaxContentHeight() {
+        if (mIsSummaryWithChildren && !mShowingPublic) {
+            return mChildrenContainer.getMaxContentHeight()
+                    + mNotificationHeader.getHeight();
+        }
         NotificationContentView showingLayout = getShowingLayout();
         return showingLayout.getMaxHeight();
     }
 
     @Override
     public int getMinHeight() {
+        if (mIsSummaryWithChildren && !mOnKeyguard) {
+            return mChildrenContainer.getMinHeight()
+                    + mNotificationHeader.getHeight();
+        }
         NotificationContentView showingLayout = getShowingLayout();
         return showingLayout.getMinHeight();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 0e32b62..5aedaf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -22,6 +22,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
+import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -32,6 +33,9 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
+import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 
 /**
  * A frame layout containing the actual payload of the notification, including the contracted,
@@ -44,8 +48,10 @@
     private static final int VISIBLE_TYPE_CONTRACTED = 0;
     private static final int VISIBLE_TYPE_EXPANDED = 1;
     private static final int VISIBLE_TYPE_HEADSUP = 2;
+    private static final int VISIBLE_TYPE_SINGLELINE = 3;
 
     private final Rect mClipBounds = new Rect();
+    private final int mSingleLineHeight;
     private final int mSmallHeight;
     private final int mHeadsUpHeight;
     private final int mRoundRectRadius;
@@ -55,10 +61,12 @@
     private View mContractedChild;
     private View mExpandedChild;
     private View mHeadsUpChild;
+    private HybridNotificationView mSingleLineView;
 
     private NotificationViewWrapper mContractedWrapper;
     private NotificationViewWrapper mExpandedWrapper;
     private NotificationViewWrapper mHeadsUpWrapper;
+    private HybridNotificationViewManager mHybridViewManager;
     private int mClipTopAmount;
     private int mContentHeight;
     private int mUnrestrictedContentHeight;
@@ -68,6 +76,8 @@
     private boolean mAnimate;
     private boolean mIsHeadsUp;
     private boolean mShowingLegacyBackground;
+    private boolean mIsChildInGroup;
+    private StatusBarNotification mStatusBarNotification;
 
     private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
             = new ViewTreeObserver.OnPreDrawListener() {
@@ -86,10 +96,14 @@
                     mRoundRectRadius);
         }
     };
+    private NotificationGroupManager mGroupManager;
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+        mSingleLineHeight = getResources().getDimensionPixelSize(
+                R.dimen.notification_single_line_height);
         mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
         mRoundRectRadius = getResources().getDimensionPixelSize(
@@ -140,6 +154,12 @@
                     MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
         }
+        if (mSingleLineView != null) {
+            int size = Math.min(maxSize, mSingleLineHeight);
+            mSingleLineView.measure(widthMeasureSpec,
+                    MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY));
+            maxChildHeight = Math.max(maxChildHeight, mSingleLineView.getMeasuredHeight());
+        }
         int ownHeight = Math.min(maxChildHeight, maxSize);
         int width = MeasureSpec.getSize(widthMeasureSpec);
         setMeasuredDimension(width, ownHeight);
@@ -271,7 +291,15 @@
     }
 
     public int getMinHeight() {
-        return mSmallHeight;
+        if (mIsChildInGroup && !isGroupExpanded()) {
+            return mSingleLineHeight;
+        } else {
+            return mSmallHeight;
+        }
+    }
+
+    private boolean isGroupExpanded() {
+        return mGroupManager.isGroupExpanded(mStatusBarNotification);
     }
 
     public void setClipTopAmount(int clipTopAmount) {
@@ -313,6 +341,7 @@
         if (visibleType != mVisibleType || force) {
             if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
                     || (visibleType == VISIBLE_TYPE_HEADSUP && mHeadsUpChild != null)
+                    || (visibleType == VISIBLE_TYPE_SINGLELINE && mSingleLineView != null)
                     || visibleType == VISIBLE_TYPE_CONTRACTED)) {
                 runSwitchAnimation(visibleType);
             } else {
@@ -339,6 +368,12 @@
             mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f);
             mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null);
         }
+        if (mSingleLineView != null) {
+            boolean singleLineVisible = visibleType == VISIBLE_TYPE_SINGLELINE;
+            mSingleLineView.setVisibility(singleLineVisible ? View.VISIBLE : View.INVISIBLE);
+            mSingleLineView.setAlpha(singleLineVisible ? 1f : 0f);
+            mSingleLineView.setLayerType(LAYER_TYPE_NONE, null);
+        }
         setLayerType(LAYER_TYPE_NONE, null);
         updateRoundRectClipping();
     }
@@ -379,6 +414,8 @@
                 return mExpandedChild;
             case VISIBLE_TYPE_HEADSUP:
                 return mHeadsUpChild;
+            case VISIBLE_TYPE_SINGLELINE:
+                return mSingleLineView;
             default:
                 return mContractedChild;
         }
@@ -396,7 +433,9 @@
                 return VISIBLE_TYPE_EXPANDED;
             }
         } else {
-            if (mContentHeight <= mSmallHeight || noExpandedChild) {
+            if (mIsChildInGroup && !isGroupExpanded()) {
+                return VISIBLE_TYPE_SINGLELINE;
+            } else if (mContentHeight <= mSmallHeight || noExpandedChild) {
                 return VISIBLE_TYPE_CONTRACTED;
             } else {
                 return VISIBLE_TYPE_EXPANDED;
@@ -405,6 +444,7 @@
     }
 
     public void notifyContentUpdated() {
+        updateSingleLineView();
         selectLayout(false /* animate */, true /* force */);
         if (mContractedChild != null) {
             mContractedWrapper.notifyContentUpdated();
@@ -443,6 +483,23 @@
         mShowingLegacyBackground = showing;
     }
 
+    public void setIsChildInGroup(boolean isChildInGroup) {
+        mIsChildInGroup = isChildInGroup;
+        updateSingleLineView();
+    }
+
+    public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
+        mStatusBarNotification = statusBarNotification;
+        updateSingleLineView();
+    }
+
+    private void updateSingleLineView() {
+        if (mIsChildInGroup) {
+            mSingleLineView = mHybridViewManager.bindFromNotification(
+                    mSingleLineView, mStatusBarNotification.getNotification());
+        }
+    }
+
     public void setSubTextVisible(boolean visible) {
         if (mExpandedChild != null) {
             mExpandedWrapper.setSubTextVisible(visible);
@@ -454,4 +511,8 @@
             mHeadsUpWrapper.setSubTextVisible(visible);
         }
     }
+
+    public void setGroupManager(NotificationGroupManager groupManager) {
+        mGroupManager = groupManager;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
new file mode 100644
index 0000000..8f46e89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -0,0 +1,128 @@
+/*
+ * 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.notification;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
+
+/**
+ * A hybrid view which may contain information about one ore more notifications.
+ */
+public class HybridNotificationView extends AlphaOptimizedFrameLayout {
+
+    protected final int mSingleLineHeight;
+    protected final int mStartMargin;
+    protected final int mEndMargin;
+    protected TextView mTitleView;
+    protected TextView mTextView;
+
+    public HybridNotificationView(Context context) {
+        this(context, null);
+    }
+
+    public HybridNotificationView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public HybridNotificationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public HybridNotificationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mSingleLineHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.notification_single_line_height);
+        mStartMargin = context.getResources().getDimensionPixelSize(
+                R.dimen.notification_content_margin_start);
+        mEndMargin = context.getResources().getDimensionPixelSize(
+                R.dimen.notification_content_margin_end);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
+        int remainingWidth = totalWidth - mStartMargin - mEndMargin;
+        int newHeightSpec = MeasureSpec.makeMeasureSpec(
+                MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST);
+        int newWidthSpec = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
+        mTitleView.measure(newWidthSpec, newHeightSpec);
+        int maxTitleLength = getResources().getDimensionPixelSize(
+                R.dimen.notification_maximum_title_length);
+        int titleWidth = mTitleView.getMeasuredWidth();
+        int heightSpec = MeasureSpec.makeMeasureSpec(mSingleLineHeight, MeasureSpec.AT_MOST);
+        boolean hasText = !TextUtils.isEmpty(mTextView.getText());
+        if (titleWidth > maxTitleLength && hasText) {
+            titleWidth = maxTitleLength;
+            int widthSpec = MeasureSpec.makeMeasureSpec(titleWidth, MeasureSpec.EXACTLY);
+            mTitleView.measure(widthSpec, heightSpec);
+        }
+        if (hasText) {
+            remainingWidth -= titleWidth;
+            int widthSpec = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
+            mTextView.measure(widthSpec, newHeightSpec);
+        }
+        setMeasuredDimension(totalWidth, mSingleLineHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        int childLeft = mStartMargin;
+        int childRight = childLeft + mTitleView.getMeasuredWidth();
+        int childBottom = (mSingleLineHeight + mTitleView.getMeasuredHeight()) / 2;
+        int childTop = childBottom - mTitleView.getMeasuredHeight();
+        int rtlLeft = transformForRtl(childLeft);
+        int rtlRight = transformForRtl(childRight);
+        mTitleView.layout(Math.min(rtlLeft, rtlRight), childTop, Math.max(rtlLeft, rtlRight),
+                childBottom);
+        childLeft = childRight;
+        childRight = childLeft + mTextView.getMeasuredWidth();
+        childTop = mTitleView.getTop() + mTitleView.getBaseline() - mTextView.getBaseline();
+        childBottom = childTop + mTextView.getMeasuredHeight();
+        rtlLeft = transformForRtl(childLeft);
+        rtlRight = transformForRtl(childRight);
+        mTextView.layout(Math.min(rtlLeft, rtlRight), childTop, Math.max(rtlLeft, rtlRight),
+                childBottom);
+    }
+
+    private int transformForRtl(int left) {
+        return isLayoutRtl() ? getWidth() - left : left;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTitleView = (TextView) findViewById(R.id.notification_title);
+        mTextView = (TextView) findViewById(R.id.notification_text);
+    }
+
+    public void bind(CharSequence title) {
+        bind(title, null);
+    }
+
+    public void bind(CharSequence title, CharSequence text) {
+        mTitleView.setText(title);
+        mTextView.setText(text);
+        requestLayout();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
new file mode 100644
index 0000000..987f7b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationViewManager.java
@@ -0,0 +1,112 @@
+/*
+ * 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.notification;
+
+import android.app.Notification;
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import java.util.List;
+
+/**
+ * A class managing {@link HybridNotificationView} views
+ */
+public class HybridNotificationViewManager {
+
+    private final Context mContext;
+    private ViewGroup mParent;
+    private String mConcadenationString;
+
+    public HybridNotificationViewManager(Context ctx, ViewGroup parent) {
+        mContext = ctx;
+        mParent = parent;
+        mConcadenationString = mContext.getString(R.string.group_summary_concadenation);
+    }
+
+    private HybridNotificationView inflateHybridView() {
+        LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+        HybridNotificationView hybrid = (HybridNotificationView) inflater.inflate(
+                R.layout.hybrid_notification, mParent, false);
+        mParent.addView(hybrid);
+        return hybrid;
+    }
+
+    public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
+            Notification notification) {
+        CharSequence titleText = resolveTitle(notification);
+        if (titleText == null) {
+            if (reusableView != null) {
+                mParent.removeView(reusableView);
+            }
+            return null;
+        }
+        if (reusableView == null) {
+            reusableView = inflateHybridView();
+        }
+        CharSequence contentText = resolveText(notification);
+        reusableView.bind(titleText, contentText);
+        return reusableView;
+    }
+
+    private CharSequence resolveText(Notification notification) {
+        CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+        if (contentText == null) {
+            contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+        }
+        return contentText;
+    }
+
+    private CharSequence resolveTitle(Notification notification) {
+        CharSequence titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
+        if (titleText == null) {
+            titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+        }
+        return titleText;
+    }
+
+    public HybridNotificationView bindFromNotificationGroup(
+            HybridNotificationView reusableView,
+            List<ExpandableNotificationRow> group, int startIndex) {
+        if (reusableView == null) {
+            reusableView = inflateHybridView();
+        }
+        CharSequence summary = null;
+        int childCount = group.size();
+        for (int i = startIndex; i < childCount; i++) {
+            ExpandableNotificationRow child = group.get(i);
+            CharSequence titleText = resolveTitle(
+                    child.getStatusBarNotification().getNotification());
+            if (titleText == null) {
+                continue;
+            }
+            if (TextUtils.isEmpty(summary)) {
+                summary = titleText;
+            } else if (reusableView.isLayoutRtl()) {
+                summary = titleText + mConcadenationString + summary;
+            } else {
+                summary = summary + mConcadenationString + titleText;
+            }
+        }
+        reusableView.bind(summary);
+        return reusableView;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index d0b3c4b..bbef1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -21,7 +21,6 @@
 
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.StatusBarState;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -93,21 +92,8 @@
         if (group.children.isEmpty()) {
             if (group.summary == null) {
                 mGroupMap.remove(groupKey);
-            } else {
-                if (group.expanded) {
-                    // only the summary is left. Change it to unexpanded in a few ms. We do this to
-                    // avoid raceconditions
-                    removed.row.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (group.children.isEmpty()) {
-                                setGroupExpanded(sbn, false);
-                            }
-                        }
-                    });
-                } else {
-                    group.summary.row.updateNotificationHeader();
-                }
+            } else if (!group.expanded) {
+                group.summary.row.updateNotificationHeader();
             }
         }
     }
@@ -155,9 +141,6 @@
     }
 
     public boolean hasGroupChildren(StatusBarNotification sbn) {
-        if (areGroupsProhibited()) {
-            return false;
-        }
         if (!sbn.getNotification().isGroupSummary()) {
             return false;
         }
@@ -168,29 +151,6 @@
         return !group.children.isEmpty();
     }
 
-    public void setStatusBarState(int newState) {
-        if (mBarState == newState) {
-            return;
-        }
-        boolean prohibitedBefore = areGroupsProhibited();
-        mBarState = newState;
-        boolean nowProhibited = areGroupsProhibited();
-        if (nowProhibited != prohibitedBefore) {
-            if (nowProhibited) {
-                for (NotificationGroup group : mGroupMap.values()) {
-                    if (group.expanded) {
-                        setGroupExpanded(group, false);
-                    }
-                }
-            }
-            mListener.onGroupsProhibitedChanged();
-        }
-    }
-
-    private boolean areGroupsProhibited() {
-        return mBarState == StatusBarState.KEYGUARD;
-    }
-
     /**
      * @return whether a given notification is a child in a group which has a summary
      */
@@ -226,6 +186,18 @@
                 : group.summary.row;
     }
 
+    public void onEntryHeadsUped(NotificationData.Entry headsUp) {
+        // TODO: handle this nicely
+    }
+
+    public void toggleGroupExpansion(StatusBarNotification sbn) {
+        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
+        if (group == null) {
+            return;
+        }
+        setGroupExpanded(group, !group.expanded);
+    }
+
     public static class NotificationGroup {
         public final HashSet<NotificationData.Entry> children = new HashSet<>();
         public NotificationData.Entry summary;
@@ -242,11 +214,6 @@
         void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
 
         /**
-         * Children group policy has changed and children may no be prohibited or allowed.
-         */
-        void onGroupsProhibitedChanged();
-
-        /**
          * A group of children just received a summary notification and should therefore become
          * children of it.
          *
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 deb4402..3e52515 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -714,7 +714,7 @@
                     R.color.notification_panel_solid_background)));
         }
 
-        mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow);
+        mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager);
         mHeadsUpManager.setBar(this);
         mHeadsUpManager.addListener(this);
         mHeadsUpManager.addListener(mNotificationPanel);
@@ -3859,7 +3859,6 @@
             clearNotificationEffects();
         }
         mState = state;
-        mGroupManager.setStatusBarState(state);
         mFalsingManager.setStatusBarState(state);
         mStatusBarWindowManager.setStatusBarState(state);
         updateDozing();
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 a515f23..dc9f5e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -33,6 +33,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 import java.io.FileDescriptor;
@@ -85,6 +86,7 @@
     private final int mStatusBarHeight;
     private final int mNotificationsTopPadding;
     private final Context mContext;
+    private final NotificationGroupManager mGroupManager;
     private PhoneStatusBar mBar;
     private int mSnoozeLengthMs;
     private ContentObserver mSettingsObserver;
@@ -104,7 +106,8 @@
     private boolean mIsObserving;
     private boolean mRemoteInputActive;
 
-    public HeadsUpManager(final Context context, View statusBarWindowView) {
+    public HeadsUpManager(final Context context, View statusBarWindowView,
+                          NotificationGroupManager groupManager) {
         mContext = context;
         Resources resources = mContext.getResources();
         mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
@@ -132,6 +135,7 @@
                 Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
                 mSettingsObserver);
         mStatusBarWindowView = statusBarWindowView;
+        mGroupManager = groupManager;
         mStatusBarHeight = resources.getDimensionPixelSize(
                 com.android.internal.R.dimen.status_bar_height);
         mNotificationsTopPadding = context.getResources()
@@ -181,12 +185,12 @@
     public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
         if (DEBUG) Log.v(TAG, "updateNotification");
 
-        headsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */);
         headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
 
         if (alert) {
             HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
             headsUpEntry.updateEntry();
+            mGroupManager.onEntryHeadsUped(headsUp);
             setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp));
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index da82456..539dace 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -24,7 +24,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
+import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -34,6 +35,10 @@
  */
 public class NotificationChildrenContainer extends ViewGroup {
 
+    private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
+    private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
+    private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
+
     private final int mChildPadding;
     private final int mDividerHeight;
     private final int mMaxNotificationHeight;
@@ -42,6 +47,12 @@
     private final int mNotificationHeaderHeight;
     private final int mNotificationAppearDistance;
     private final float mHeaderTopPaddingSubstraction;
+    private final HybridNotificationViewManager mHybridViewManager;
+    private final float mCollapsedBottompadding;
+    private boolean mChildrenExpanded;
+    private ExpandableNotificationRow mNotificationParent;
+    private HybridNotificationView mGroupOverflowContainer;
+    private ViewState mGroupOverFlowState;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -69,6 +80,8 @@
         mNotificationHeaderHeight = getResources().getDimensionPixelSize(
                 R.dimen.notification_header_height);
         mHeaderTopPaddingSubstraction = 2 * getResources().getDisplayMetrics().density;
+        mCollapsedBottompadding = 10 * getResources().getDisplayMetrics().density;
+        mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
     }
 
     @Override
@@ -78,14 +91,6 @@
         for (int i = 0; i < childCount; i++) {
             View child = mChildren.get(i);
             boolean viewGone = child.getVisibility() == View.GONE;
-            if (i != 0) {
-                View divider = mDividers.get(i - 1);
-                int dividerVisibility = divider.getVisibility();
-                int newVisibility = viewGone ? INVISIBLE : VISIBLE;
-                if (dividerVisibility != newVisibility) {
-                    divider.setVisibility(newVisibility);
-                }
-            }
             if (viewGone) {
                 continue;
             }
@@ -96,6 +101,10 @@
                 firstChild = false;
             }
         }
+        if (mGroupOverflowContainer != null) {
+            mGroupOverflowContainer.layout(0, 0, getWidth(),
+                    mGroupOverflowContainer.getMeasuredHeight());
+        }
     }
 
     @Override
@@ -115,9 +124,6 @@
         boolean firstChild = true;
         for (int i = 0; i < childCount; i++) {
             View child = mChildren.get(i);
-            if (child.getVisibility() == View.GONE) {
-                continue;
-            }
             child.measure(widthMeasureSpec, newHeightSpec);
             height += child.getMeasuredHeight();
             if (!firstChild) {
@@ -130,9 +136,9 @@
             }
         }
         int width = MeasureSpec.getSize(widthMeasureSpec);
-        height = hasFixedHeight ? ownMaxHeight
-                : isHeightLimited ? Math.min(ownMaxHeight, height)
-                : height;
+        if (mGroupOverflowContainer != null) {
+            mGroupOverflowContainer.measure(widthMeasureSpec, newHeightSpec);
+        }
         setMeasuredDimension(width, height);
     }
 
@@ -151,8 +157,7 @@
             addView(divider);
             mDividers.add(Math.max(newIndex - 1, 0), divider);
         }
-        // TODO: adapt background corners
-        // TODO: fix overdraw
+        updateGroupOverflow();
     }
 
     public void removeNotification(ExpandableNotificationRow row) {
@@ -164,7 +169,26 @@
             removeView(divider);
         }
         row.setSystemChildExpanded(false);
-        // TODO: adapt background corners
+        updateGroupOverflow();
+    }
+
+    public void updateGroupOverflow() {
+        int childCount = mChildren.size();
+        int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
+        boolean hasOverflow = childCount > maxAllowedVisibleChildren;
+        int lastVisibleIndex = hasOverflow ? maxAllowedVisibleChildren - 2
+                : maxAllowedVisibleChildren - 1;
+        if (hasOverflow) {
+            mGroupOverflowContainer = mHybridViewManager.bindFromNotificationGroup(
+                    mGroupOverflowContainer, mChildren, lastVisibleIndex + 1);
+            if (mGroupOverFlowState == null) {
+                mGroupOverFlowState = new ViewState();
+            }
+        } else if (mGroupOverflowContainer != null) {
+            removeView(mGroupOverflowContainer);
+            mGroupOverflowContainer = null;
+            mGroupOverFlowState = null;
+        }
     }
 
     private View inflateDivider() {
@@ -196,25 +220,42 @@
                 result = true;
             }
         }
-
-        // Let's make the first child expanded!
-        boolean first = true;
-        for (int i = 0; i < childOrder.size(); i++) {
-            ExpandableNotificationRow child = childOrder.get(i);
-            child.setSystemChildExpanded(first);
-            first = false;
-        }
+        updateExpansionStates();
         return result;
     }
 
+    private void updateExpansionStates() {
+        // Let's make the first child expanded if the parent is
+        for (int i = 0; i < mChildren.size(); i++) {
+            ExpandableNotificationRow child = mChildren.get(i);
+            child.setSystemChildExpanded(false);
+        }
+    }
+
+    /**
+     *
+     * @return the intrinsic size of this children container, i.e the natural fully expanded state
+     */
     public int getIntrinsicHeight() {
-        int childCount = mChildren.size();
+        int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
+        return getIntrinsicHeight(maxAllowedVisibleChildren);
+    }
+
+    /**
+     * @return the intrinsic height with a number of children given
+     *         in @param maxAllowedVisibleChildren
+     */
+    private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
         int intrinsicHeight = 0;
         int visibleChildren = 0;
+        int childCount = mChildren.size();
         for (int i = 0; i < childCount; i++) {
+            if (visibleChildren >= maxAllowedVisibleChildren) {
+                break;
+            }
             ExpandableNotificationRow child = mChildren.get(i);
-            if (child.getVisibility() == View.GONE) {
-                continue;
+            if (i == 0 && child.hasSameBgColor(mNotificationParent)) {
+                intrinsicHeight -= mHeaderTopPaddingSubstraction;
             }
             intrinsicHeight += child.getIntrinsicHeight();
             visibleChildren++;
@@ -222,6 +263,9 @@
         if (visibleChildren > 0) {
             intrinsicHeight += (visibleChildren - 1) * mDividerHeight;
         }
+        if (!mChildrenExpanded) {
+            intrinsicHeight += mCollapsedBottompadding;
+        }
         return intrinsicHeight;
     }
 
@@ -235,15 +279,21 @@
         int childCount = mChildren.size();
         int yPosition = mNotificationHeaderHeight;
         boolean firstChild = true;
+        int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
+        boolean hasOverflow = !mChildrenExpanded && childCount > maxAllowedVisibleChildren
+                && maxAllowedVisibleChildren != NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
+        int lastVisibleIndex = hasOverflow
+                ? maxAllowedVisibleChildren - 2
+                : maxAllowedVisibleChildren - 1;
         for (int i = 0; i < childCount; i++) {
             ExpandableNotificationRow child = mChildren.get(i);
-            if (child.getVisibility() == View.GONE) {
-                continue;
-            }
             if (!firstChild) {
                 // There's a divider
                 yPosition += mChildPadding;
             } else {
+                if (child.hasSameBgColor(mNotificationParent)) {
+                    yPosition -= mHeaderTopPaddingSubstraction;
+                }
                 firstChild = false;
             }
             StackViewState childState = resultState.getViewStateForView(child);
@@ -255,37 +305,62 @@
             childState.dark = parentState.dark;
             childState.hideSensitive = parentState.hideSensitive;
             childState.belowSpeedBump = parentState.belowSpeedBump;
-            childState.scale =  parentState.scale;
+            childState.scale =  1.0f;
             childState.clipTopAmount = 0;
             childState.topOverLap = 0;
+            boolean visible = i <= lastVisibleIndex;
+            childState.alpha = visible ? 1 : 0;
             childState.location = parentState.location;
             yPosition += intrinsicHeight;
         }
+        if (mGroupOverflowContainer != null) {
+            mGroupOverFlowState.initFrom(mGroupOverflowContainer);
+            if (hasOverflow) {
+                StackViewState firstOverflowState =
+                        resultState.getViewStateForView(mChildren.get(lastVisibleIndex + 1));
+                mGroupOverFlowState.yTranslation = firstOverflowState.yTranslation;
+            }
+            mGroupOverFlowState.alpha = mChildrenExpanded || !hasOverflow ? 0.0f : 1.0f;
+        }
+    }
+
+    private int getMaxAllowedVisibleChildren() {
+        return getMaxAllowedVisibleChildren(false /* likeCollapsed */);
+    }
+
+    private int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
+        if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) {
+            return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
+        }
+        if (mNotificationParent.isExpanded()) {
+            return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
+        }
+        return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
     }
 
     public void applyState(StackScrollState state) {
         int childCount = mChildren.size();
         boolean firstChild = true;
-        ViewState dividerState = new ViewState();
+        ViewState tmpState = new ViewState();
         for (int i = 0; i < childCount; i++) {
             ExpandableNotificationRow child = mChildren.get(i);
             StackViewState viewState = state.getViewStateForView(child);
-            if (child.getVisibility() == View.GONE) {
-                continue;
-            }
             if (!firstChild) {
                 // layout the divider
                 View divider = mDividers.get(i - 1);
-                dividerState.initFrom(divider);
-                dividerState.yTranslation = (int) (viewState.yTranslation
+                tmpState.initFrom(divider);
+                tmpState.yTranslation = (int) (viewState.yTranslation
                         - (mChildPadding + mDividerHeight) / 2.0f);
-                dividerState.alpha = 1;
-                state.applyViewState(divider, dividerState);
+                tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
+                state.applyViewState(divider, tmpState);
             } else {
                 firstChild = false;
             }
             state.applyState(child, viewState);
         }
+        if (mGroupOverflowContainer != null) {
+            state.applyViewState(mGroupOverflowContainer, mGroupOverFlowState);
+        }
     }
 
     /**
@@ -295,6 +370,10 @@
      * @param state the new state we animate to
      */
     public void prepareExpansionChanged(StackScrollState state) {
+        if (true) {
+            // TODO: do something that makes sense
+            return;
+        }
         int childCount = mChildren.size();
         boolean firstChild = true;
         StackViewState sourceState = new StackViewState();
@@ -302,9 +381,6 @@
         for (int i = 0; i < childCount; i++) {
             ExpandableNotificationRow child = mChildren.get(i);
             StackViewState viewState = state.getViewStateForView(child);
-            if (child.getVisibility() == View.GONE) {
-                continue;
-            }
             if (!firstChild) {
                 // layout the divider
                 View divider = mDividers.get(i - 1);
@@ -321,23 +397,17 @@
             sourceState.yTranslation += mNotificationAppearDistance;
             state.applyState(child, sourceState);
         }
-        mCollapseButton.setAlpha(0);
-        mCollapseDivider.setAlpha(0);
-        mCollapseDivider.setTranslationY(mNotificationAppearDistance / 4);
     }
 
     public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
             boolean withDelays, long baseDelay, long duration) {
         int childCount = mChildren.size();
         boolean firstChild = true;
-        ViewState dividerState = new ViewState();
+        ViewState tmpState = new ViewState();
         int notGoneIndex = 0;
         for (int i = 0; i < childCount; i++) {
             ExpandableNotificationRow child = mChildren.get(i);
             StackViewState viewState = state.getViewStateForView(child);
-            if (child.getVisibility() == View.GONE) {
-                continue;
-            }
             int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN,
                     notGoneIndex + 1);
             long delay = withDelays
@@ -347,17 +417,20 @@
             if (!firstChild) {
                 // layout the divider
                 View divider = mDividers.get(i - 1);
-                dividerState.initFrom(divider);
-                dividerState.yTranslation = viewState.yTranslation
+                tmpState.initFrom(divider);
+                tmpState.yTranslation = viewState.yTranslation
                         - (mChildPadding + mDividerHeight) / 2.0f;
-                dividerState.alpha = 1;
-                stateAnimator.startViewAnimations(divider, dividerState, delay, duration);
+                tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;;
+                stateAnimator.startViewAnimations(divider, tmpState, delay, duration);
             } else {
                 firstChild = false;
             }
             stateAnimator.startStackAnimations(child, viewState, state, -1, delay);
             notGoneIndex++;
         }
+        if (mGroupOverflowContainer != null) {
+            stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState, -1, 0);
+        }
     }
 
     public ExpandableNotificationRow getViewAtPosition(float y) {
@@ -375,7 +448,15 @@
         return null;
     }
 
-    public void setTintColor(int color) {
-        ExpandableNotificationRow.applyTint(mCollapseDivider, color);
+    public void setChildrenExpanded(boolean childrenExpanded) {
+        mChildrenExpanded = childrenExpanded;
+    }
+
+    public int getMaxContentHeight() {
+        return getIntrinsicHeight(NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
+    }
+
+    public int getMinHeight() {
+        return getIntrinsicHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */));
     }
 }
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 1b66073..573e45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1726,7 +1726,7 @@
             ExpandableNotificationRow groupSummary =
                     mGroupManager.getGroupSummary(row.getStatusBarNotification());
             if (groupSummary != null && groupSummary != row) {
-                return !groupSummary.areChildrenExpanded();
+                return row.getVisibility() == View.INVISIBLE;
             }
         }
         return false;
@@ -2800,10 +2800,6 @@
     }
 
     @Override
-    public void onGroupsProhibitedChanged() {
-    }
-
-    @Override
     public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
         for (NotificationData.Entry entry : group.children) {
             ExpandableNotificationRow row = entry.row;
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 cf696a1..65ca95b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -361,7 +361,7 @@
                     // handle the notgoneIndex for the children as well
                     List<ExpandableNotificationRow> children =
                             row.getNotificationChildren();
-                    if (row.areChildrenExpanded() && children != null) {
+                    if (row.isSummaryWithChildren() && children != null) {
                         for (ExpandableNotificationRow childRow : children) {
                             if (childRow.getVisibility() != View.GONE) {
                                 StackViewState childState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 3768ca4..e155d70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -65,7 +65,7 @@
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
                 List<ExpandableNotificationRow> children =
                         row.getNotificationChildren();
-                if (row.areChildrenExpanded() && children != null) {
+                if (row.isSummaryWithChildren() && children != null) {
                     for (ExpandableNotificationRow childRow : children) {
                         resetViewState(childRow);
                     }