AOD: Notification groups on Ambient Display

Bug: 36690937
Test: receive notification group on ambient display
Change-Id: I9dd91b85432e3d4309e0eb78a7b03ada87c3554e
diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml
index 476f52b..f4501da 100644
--- a/packages/SystemUI/res/layout/hybrid_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_notification.xml
@@ -19,23 +19,22 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingStart="@*android:dimen/notification_content_margin_start"
-    android:paddingEnd="12dp"
-    android:gravity="bottom|start">
+    android:gravity="bottom|start"
+    style="?attr/hybridNotificationStyle">
     <TextView
         android:id="@+id/notification_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingEnd="4dp"
-        android:singleLine="true"
         android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+        android:singleLine="true"
+        style="?attr/hybridNotificationTitleStyle"
     />
     <TextView
         android:id="@+id/notification_text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingEnd="4dp"
-        android:textAppearance="@*android:style/TextAppearance.Material.Notification"
         android:singleLine="true"
-        />
-</com.android.systemui.statusbar.notification.HybridNotificationView>
+        android:textAppearance="@*android:style/TextAppearance.Material.Notification"
+        style="?attr/hybridNotificationTextStyle"
+    />
+</com.android.systemui.statusbar.notification.HybridNotificationView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hybrid_overflow_number.xml b/packages/SystemUI/res/layout/hybrid_overflow_number.xml
index f3dde8d..792f424 100644
--- a/packages/SystemUI/res/layout/hybrid_overflow_number.xml
+++ b/packages/SystemUI/res/layout/hybrid_overflow_number.xml
@@ -20,7 +20,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:textAppearance="@*android:style/TextAppearance.Material.Notification"
-    android:paddingEnd="@*android:dimen/notification_content_margin_end"
+    android:paddingEnd="@dimen/group_overflow_number_padding"
     android:gravity="end"
     android:singleLine="true"
     />
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 008fcf0..a57b17e 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -108,6 +108,12 @@
         <attr name="android:layout" />
     </declare-styleable>
 
+    <declare-styleable name="HybridNotificationTheme">
+        <attr name="hybridNotificationStyle" format="reference" />
+        <attr name="hybridNotificationTitleStyle" format="reference" />
+        <attr name="hybridNotificationTextStyle" format="reference" />
+    </declare-styleable>
+
     <declare-styleable name="AutoSizingList">
         <!-- Whether AutoSizingList will show only as many items as fit on screen and
              remove extra items instead of scrolling. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f072849..5374673 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -57,6 +57,11 @@
     <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
     <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
 
+    <dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen>
+    <dimen name="group_overflow_number_size_dark">16sp</dimen>
+    <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen>
+    <dimen name="group_overflow_number_extra_padding_dark">@*android:dimen/notification_extra_margin_ambient</dimen>
+
     <!-- max height of a notification such that the content can still fade out when closing -->
     <dimen name="max_notification_fadeout_height">100dp</dimen>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index dbdbd1e..9650cea 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -82,6 +82,56 @@
         <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
     </style>
 
+    <!-- HybridNotification themes and styles -->
+
+    <style name="HybridNotification">
+        <item name="hybridNotificationStyle">@style/hybrid_notification</item>
+        <item name="hybridNotificationTitleStyle">@style/hybrid_notification_title</item>
+        <item name="hybridNotificationTextStyle">@style/hybrid_notification_text</item>
+    </style>
+
+    <style name="HybridNotification.Ambient">
+        <item name="hybridNotificationStyle">@style/hybrid_notification_ambient</item>
+        <item name="hybridNotificationTitleStyle">@style/hybrid_notification_title_ambient</item>
+        <item name="hybridNotificationTextStyle">@style/hybrid_notification_text_ambient</item>
+    </style>
+
+    <style name="hybrid_notification_ambient">
+        <item name="android:paddingStart">@*android:dimen/notification_extra_margin_ambient</item>
+        <item name="android:paddingEnd">@*android:dimen/notification_extra_margin_ambient</item>
+        <item name="android:orientation">vertical</item>
+        <item name="android:paddingBottom">23.5dp</item>
+    </style>
+
+    <style name="hybrid_notification">
+        <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
+        <item name="android:paddingEnd">12dp</item>
+    </style>
+
+    <style name="hybrid_notification_title_ambient">
+        <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
+        <item name="android:paddingEnd">@*android:dimen/notification_content_margin_end</item>
+        <item name="android:textSize">20sp</item>
+        <item name="android:textColor">#ffffffff</item>
+    </style>
+
+    <style name="hybrid_notification_title">
+        <item name="android:paddingEnd">4dp</item>
+    </style>
+
+    <style name="hybrid_notification_text_ambient">
+        <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
+        <item name="android:paddingEnd">@*android:dimen/notification_content_margin_end</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">#eeffffff</item>
+        <item name="android:layout_marginTop">4dp</item>
+    </style>
+
+    <style name="hybrid_notification_text">
+        <item name="android:paddingEnd">4dp</item>
+    </style>
+
+
     <style name="TextAppearance.StatusBar.HeadsUp"
         parent="@*android:style/TextAppearance.StatusBar">
     </style>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 9368747..208be8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -227,6 +227,7 @@
     private boolean mUseIncreasedHeadsUpHeight;
     private float mTranslationWhenRemoved;
     private boolean mWasChildInGroupWhenRemoved;
+    private int mNotificationColorAmbient;
 
     @Override
     public boolean isGroupExpansionChanging() {
@@ -862,12 +863,18 @@
         mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
                 getStatusBarNotification().getNotification().color,
                 getBackgroundColorWithoutTint());
+        mNotificationColorAmbient = NotificationColorUtil.resolveAmbientColor(mContext,
+                getStatusBarNotification().getNotification().color);
     }
 
     public HybridNotificationView getSingleLineView() {
         return mPrivateLayout.getSingleLineView();
     }
 
+    public HybridNotificationView getAmbientSingleLineView() {
+        return getShowingLayout().getAmbientSingleLineChild();
+    }
+
     public boolean isOnKeyguard() {
         return mOnKeyguard;
     }
@@ -1131,6 +1138,10 @@
         return mNotificationInflater;
     }
 
+    public int getNotificationColorAmbient() {
+        return mNotificationColorAmbient;
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
@@ -1509,13 +1520,11 @@
             return mGuts.getIntrinsicHeight();
         } else if ((isChildInGroup() && !isGroupExpanded())) {
             return mPrivateLayout.getMinHeight();
-        } else if (mShowAmbient) {
-            return getAmbientHeight();
         } else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
             return getMinHeight();
-        } else if (mIsSummaryWithChildren && !mOnKeyguard) {
+        } else if (mIsSummaryWithChildren && (!mOnKeyguard || mShowAmbient)) {
             return mChildrenContainer.getIntrinsicHeight();
-        } else if (!mOnKeyguard && (mIsHeadsUp || mHeadsupDisappearRunning)) {
+        } else if (isHeadsUpAllowed() && (mIsHeadsUp || mHeadsupDisappearRunning)) {
             if (isPinned() || mHeadsupDisappearRunning) {
                 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
             } else if (isExpanded()) {
@@ -1530,6 +1539,10 @@
         }
     }
 
+    private boolean isHeadsUpAllowed() {
+        return !mOnKeyguard && !mShowAmbient;
+    }
+
     @Override
     public boolean isGroupExpanded() {
         return mGroupManager.isGroupExpanded(mStatusBarNotification);
@@ -1849,24 +1862,17 @@
     public int getMinHeight() {
         if (mGuts != null && mGuts.isExposed()) {
             return mGuts.getIntrinsicHeight();
-        } else if (!mOnKeyguard && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+        } else if (isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
             return mChildrenContainer.getMinHeight();
-        } else if (!mOnKeyguard && mIsHeadsUp) {
+        } else if (isHeadsUpAllowed() && mIsHeadsUp) {
             return mHeadsUpHeight;
         }
         NotificationContentView showingLayout = getShowingLayout();
         return showingLayout.getMinHeight();
     }
 
-    private int getAmbientHeight() {
-        NotificationContentView showingLayout = getShowingLayout();
-        return showingLayout.getAmbientChild() != null
-                ? showingLayout.getAmbientChild().getHeight()
-                : getCollapsedHeight();
-    }
-
     @Override
     public int getCollapsedHeight() {
         if (mIsSummaryWithChildren && !mShowingPublic) {
@@ -2101,10 +2107,17 @@
     public void setShowAmbient(boolean showAmbient) {
         if (showAmbient != mShowAmbient) {
             mShowAmbient = showAmbient;
+            if (mChildrenContainer != null) {
+                mChildrenContainer.notifyShowAmbientChanged();
+            }
             notifyHeightChanged(false /* needsAnimation */);
         }
     }
 
+    public boolean isShowingAmbient() {
+        return mShowAmbient;
+    }
+
     public void setAboveShelf(boolean aboveShelf) {
         mAboveShelf = aboveShelf;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index e7bf983..baf0c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -54,6 +54,7 @@
     private static final int VISIBLE_TYPE_HEADSUP = 2;
     private static final int VISIBLE_TYPE_SINGLELINE = 3;
     private static final int VISIBLE_TYPE_AMBIENT = 4;
+    private static final int VISIBLE_TYPE_AMBIENT_SINGLELINE = 5;
     public static final int UNDEFINED = -1;
 
     private final Rect mClipBounds = new Rect();
@@ -65,6 +66,7 @@
     private View mHeadsUpChild;
     private HybridNotificationView mSingleLineView;
     private View mAmbientChild;
+    private HybridNotificationView mAmbientSingleLineChild;
 
     private RemoteInputView mExpandedRemoteInput;
     private RemoteInputView mHeadsUpRemoteInput;
@@ -252,6 +254,27 @@
                             : MeasureSpec.AT_MOST));
             maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
         }
+        if (mAmbientSingleLineChild != null) {
+            int size = Math.min(maxSize, mNotificationAmbientHeight);
+            ViewGroup.LayoutParams layoutParams = mAmbientSingleLineChild.getLayoutParams();
+            boolean useExactly = false;
+            if (layoutParams.height >= 0) {
+                // An actual height is set
+                size = Math.min(size, layoutParams.height);
+                useExactly = true;
+            }
+            int ambientSingleLineWidthSpec = widthMeasureSpec;
+            if (mSingleLineWidthIndention != 0
+                    && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
+                ambientSingleLineWidthSpec = MeasureSpec.makeMeasureSpec(
+                        width - mSingleLineWidthIndention + mAmbientSingleLineChild.getPaddingEnd(),
+                        MeasureSpec.EXACTLY);
+            }
+            mAmbientSingleLineChild.measure(ambientSingleLineWidthSpec,
+                    MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+                            : MeasureSpec.AT_MOST));
+            maxChildHeight = Math.max(maxChildHeight, mAmbientSingleLineChild.getMeasuredHeight());
+        }
         int ownHeight = Math.min(maxChildHeight, maxSize);
         setMeasuredDimension(width, ownHeight);
     }
@@ -345,6 +368,10 @@
         return mAmbientChild;
     }
 
+    public HybridNotificationView getAmbientSingleLineChild() {
+        return mAmbientSingleLineChild;
+    }
+
     public void setContractedChild(View child) {
         if (mContractedChild != null) {
             mContractedChild.animate().cancel();
@@ -533,6 +560,9 @@
         int hint;
         if (mAmbientChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_AMBIENT)) {
             hint = mAmbientChild.getHeight();
+        } else if (mAmbientSingleLineChild != null && isVisibleOrTransitioning(
+                VISIBLE_TYPE_AMBIENT_SINGLELINE)) {
+            hint = mAmbientSingleLineChild.getHeight();
         } else if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
             hint = mHeadsUpChild.getHeight();
         } else if (mExpandedChild != null) {
@@ -622,7 +652,9 @@
     }
 
     public int getMaxHeight() {
-        if (mExpandedChild != null) {
+        if (mContainingNotification.isShowingAmbient()) {
+            return getShowingAmbientView().getHeight();
+        } else if (mExpandedChild != null) {
             return mExpandedChild.getHeight();
         } else if (mIsHeadsUp && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) {
             return mHeadsUpChild.getHeight();
@@ -635,13 +667,24 @@
     }
 
     public int getMinHeight(boolean likeGroupExpanded) {
-        if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
+        if (mContainingNotification.isShowingAmbient()) {
+            return getShowingAmbientView().getHeight();
+        } else if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
             return mContractedChild.getHeight();
         } else {
             return mSingleLineView.getHeight();
         }
     }
 
+    public View getShowingAmbientView() {
+        View v = mIsChildInGroup ? mAmbientSingleLineChild : mAmbientChild;
+        if (v != null) {
+            return v;
+        } else {
+            return mContractedChild;
+        }
+    }
+
     private boolean isGroupExpanded() {
         return mGroupManager.isGroupExpanded(mStatusBarNotification);
     }
@@ -723,6 +766,8 @@
         forceUpdateVisibility(VISIBLE_TYPE_HEADSUP, mHeadsUpChild, mHeadsUpWrapper);
         forceUpdateVisibility(VISIBLE_TYPE_SINGLELINE, mSingleLineView, mSingleLineView);
         forceUpdateVisibility(VISIBLE_TYPE_AMBIENT, mAmbientChild, mAmbientWrapper);
+        forceUpdateVisibility(VISIBLE_TYPE_AMBIENT_SINGLELINE, mAmbientSingleLineChild,
+                mAmbientSingleLineChild);
         fireExpandedVisibleListenerIfVisible();
         // forceUpdateVisibilities cancels outstanding animations without updating the
         // mAnimationStartVisibleType. Do so here instead.
@@ -791,6 +836,8 @@
                 mSingleLineView, mSingleLineView);
         updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT,
                 mAmbientChild, mAmbientWrapper);
+        updateViewVisibility(visibleType, VISIBLE_TYPE_AMBIENT_SINGLELINE,
+                mAmbientSingleLineChild, mAmbientSingleLineChild);
         fireExpandedVisibleListenerIfVisible();
         // updateViewVisibilities cancels outstanding animations without updating the
         // mAnimationStartVisibleType. Do so here instead.
@@ -853,6 +900,8 @@
                 return mSingleLineView;
             case VISIBLE_TYPE_AMBIENT:
                 return mAmbientWrapper;
+            case VISIBLE_TYPE_AMBIENT_SINGLELINE:
+                return mAmbientSingleLineChild;
             default:
                 return mContractedWrapper;
         }
@@ -872,6 +921,8 @@
                 return mSingleLineView;
             case VISIBLE_TYPE_AMBIENT:
                 return mAmbientChild;
+            case VISIBLE_TYPE_AMBIENT_SINGLELINE:
+                return mAmbientSingleLineChild;
             default:
                 return mContractedChild;
         }
@@ -896,9 +947,14 @@
      * @return one of the static enum types in this view, calculated form the current state
      */
     public int calculateVisibleType() {
-        if (mDark && !mIsChildInGroup) {
-            // TODO: Handle notification groups
-            return VISIBLE_TYPE_AMBIENT;
+        if (mContainingNotification.isShowingAmbient()) {
+            if (mIsChildInGroup && mAmbientSingleLineChild != null) {
+                return VISIBLE_TYPE_AMBIENT_SINGLELINE;
+            } else if (mAmbientChild != null) {
+                return VISIBLE_TYPE_AMBIENT;
+            } else {
+                return VISIBLE_TYPE_CONTRACTED;
+            }
         }
         if (mUserExpanding) {
             int height = !mIsChildInGroup || isGroupExpanded()
@@ -1021,13 +1077,13 @@
         if (mAmbientChild != null) {
             mAmbientWrapper.setIsChildInGroup(mIsChildInGroup);
         }
-        updateSingleLineView();
+        updateAllSingleLineViews();
     }
 
     public void onNotificationUpdated(NotificationData.Entry entry) {
         mStatusBarNotification = entry.notification;
         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
-        updateSingleLineView();
+        updateAllSingleLineViews();
         if (mContractedChild != null) {
             mContractedWrapper.notifyContentUpdated(entry.row);
         }
@@ -1048,6 +1104,10 @@
         mPreviousHeadsUpRemoteInputIntent = null;
     }
 
+    private void updateAllSingleLineViews() {
+        updateSingleLineView();
+        updateAmbientSingleLineView();
+    }
     private void updateSingleLineView() {
         if (mIsChildInGroup) {
             mSingleLineView = mHybridGroupManager.bindFromNotification(
@@ -1058,6 +1118,16 @@
         }
     }
 
+    private void updateAmbientSingleLineView() {
+        if (mIsChildInGroup) {
+            mAmbientSingleLineChild = mHybridGroupManager.bindAmbientFromNotification(
+                    mAmbientSingleLineChild, mStatusBarNotification.getNotification());
+        } else if (mAmbientSingleLineChild != null) {
+            removeView(mAmbientSingleLineChild);
+            mAmbientSingleLineChild = null;
+        }
+    }
+
     private void applyRemoteInput(final NotificationData.Entry entry) {
         if (mRemoteInputController == null) {
             return;
@@ -1255,7 +1325,7 @@
         if (mIsChildInGroup && mSingleLineView != null) {
             removeView(mSingleLineView);
             mSingleLineView = null;
-            updateSingleLineView();
+            updateAllSingleLineViews();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
index 7373607..3ed8cce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
@@ -18,20 +18,14 @@
 
 import android.app.Notification;
 import android.content.Context;
-import android.text.BidiFormatter;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-
-import java.util.List;
 
 /**
  * A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -40,16 +34,37 @@
 public class HybridGroupManager {
 
     private final Context mContext;
-    private ViewGroup mParent;
+    private final NotificationDozeHelper mDozer;
+    private final ViewGroup mParent;
+
+    private final float mOverflowNumberSizeDark;
+    private final int mOverflowNumberPaddingDark;
+    private final float mOverflowNumberSize;
+    private final int mOverflowNumberPadding;
+
     private int mOverflowNumberColor;
+    private int mOverflowNumberColorDark;
+    private float mDarkAmount = 0f;
 
     public HybridGroupManager(Context ctx, ViewGroup parent) {
         mContext = ctx;
         mParent = parent;
+        mDozer = new NotificationDozeHelper();
+
+        Resources res = mContext.getResources();
+        mOverflowNumberSize = res.getDimensionPixelSize(
+                R.dimen.group_overflow_number_size);
+        mOverflowNumberSizeDark = res.getDimensionPixelSize(
+                R.dimen.group_overflow_number_size_dark);
+        mOverflowNumberPadding = res.getDimensionPixelSize(
+                R.dimen.group_overflow_number_padding);
+        mOverflowNumberPaddingDark = mOverflowNumberPadding + res.getDimensionPixelSize(
+                R.dimen.group_overflow_number_extra_padding_dark);
     }
 
-    private HybridNotificationView inflateHybridView() {
-        LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+    private HybridNotificationView inflateHybridViewWithStyle(int style) {
+        LayoutInflater inflater = new ContextThemeWrapper(mContext, style)
+                .getSystemService(LayoutInflater.class);
         HybridNotificationView hybrid = (HybridNotificationView) inflater.inflate(
                 R.layout.hybrid_notification, mParent, false);
         mParent.addView(hybrid);
@@ -66,11 +81,13 @@
     }
 
     private void updateOverFlowNumberColor(TextView numberView) {
-        numberView.setTextColor(mOverflowNumberColor);
+        numberView.setTextColor(NotificationUtils.interpolateColors(
+                mOverflowNumberColor, mOverflowNumberColorDark, mDarkAmount));
     }
 
-    public void setOverflowNumberColor(TextView numberView, int overflowNumberColor) {
-        mOverflowNumberColor = overflowNumberColor;
+    public void setOverflowNumberColor(TextView numberView, int colorRegular, int colorDark) {
+        mOverflowNumberColor = colorRegular;
+        mOverflowNumberColorDark = colorDark;
         if (numberView != null) {
             updateOverFlowNumberColor(numberView);
         }
@@ -78,8 +95,20 @@
 
     public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
             Notification notification) {
+        return bindFromNotificationWithStyle(reusableView, notification,
+                R.style.HybridNotification);
+    }
+
+    public HybridNotificationView bindAmbientFromNotification(HybridNotificationView reusableView,
+            Notification notification) {
+        return bindFromNotificationWithStyle(reusableView, notification,
+                R.style.HybridNotification_Ambient);
+    }
+
+    private HybridNotificationView bindFromNotificationWithStyle(
+            HybridNotificationView reusableView, Notification notification, int style) {
         if (reusableView == null) {
-            reusableView = inflateHybridView();
+            reusableView = inflateHybridViewWithStyle(style);
         }
         CharSequence titleText = resolveTitle(notification);
         CharSequence contentText = resolveText(notification);
@@ -118,4 +147,16 @@
         reusableView.setContentDescription(contentDescription);
         return reusableView;
     }
+
+    public void setOverflowNumberDark(TextView view, boolean dark, boolean fade, long delay) {
+        mDozer.setIntensityDark((f)->{
+            mDarkAmount = f;
+            updateOverFlowNumberColor(view);
+        }, dark, fade, delay);
+        view.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+                dark ? mOverflowNumberSizeDark : mOverflowNumberSize);
+        int paddingEnd = dark ? mOverflowNumberPaddingDark : mOverflowNumberPadding;
+        view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(), paddingEnd,
+                view.getPaddingBottom());
+    }
 }
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 fe249a6..cb1f44e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -30,7 +30,6 @@
 import android.widget.TextView;
 
 import com.android.systemui.R;
-import com.android.systemui.ViewInvertHelper;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationHeaderUtil;
@@ -39,7 +38,6 @@
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -52,6 +50,7 @@
     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 static final int NUMBER_OF_CHILDREN_WHEN_AMBIENT = 3;
     private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
         private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
 
@@ -70,7 +69,6 @@
     private int mNotificationHeaderMargin;
     private int mNotificatonTopPadding;
     private float mCollapsedBottompadding;
-    private ViewInvertHelper mOverflowInvertHelper;
     private boolean mChildrenExpanded;
     private ExpandableNotificationRow mContainingNotification;
     private TextView mOverflowNumber;
@@ -85,12 +83,14 @@
     private NotificationViewWrapper mNotificationHeaderWrapper;
     private NotificationHeaderView mNotificationHeaderLowPriority;
     private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
+    private ViewGroup mNotificationHeaderAmbient;
+    private NotificationViewWrapper mNotificationHeaderWrapperAmbient;
     private NotificationHeaderUtil mHeaderUtil;
     private ViewState mHeaderViewState;
     private int mClipBottomAmount;
     private boolean mIsLowPriority;
     private OnClickListener mHeaderClickListener;
-    private boolean mShowingNormalHeader;
+    private ViewGroup mCurrentHeader;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -152,6 +152,11 @@
                     mNotificationHeaderLowPriority.getMeasuredWidth(),
                     mNotificationHeaderLowPriority.getMeasuredHeight());
         }
+        if (mNotificationHeaderAmbient != null) {
+            mNotificationHeaderAmbient.layout(0, 0,
+                    mNotificationHeaderAmbient.getMeasuredWidth(),
+                    mNotificationHeaderAmbient.getMeasuredHeight());
+        }
     }
 
     @Override
@@ -203,6 +208,10 @@
             headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
             mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
         }
+        if (mNotificationHeaderAmbient != null) {
+            headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
+            mNotificationHeaderAmbient.measure(widthMeasureSpec, headerHeightSpec);
+        }
 
         setMeasuredDimension(width, height);
     }
@@ -273,7 +282,7 @@
         StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
         final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
                 notification.getNotification());
-        RemoteViews header = builder.makeNotificationHeader();
+        RemoteViews header = builder.makeNotificationHeader(false /* ambient */);
         if (mNotificationHeader == null) {
             mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
             final View expandButton = mNotificationHeader.findViewById(
@@ -289,10 +298,33 @@
         }
         mNotificationHeaderWrapper.notifyContentUpdated(mContainingNotification);
         recreateLowPriorityHeader(builder);
-        updateHeaderVisibility(false /* animate */);
+        recreateAmbientHeader(builder);
+        resetHeaderVisibilityIfNeeded(mNotificationHeader, calculateDesiredHeader());
         updateChildrenHeaderAppearance();
     }
 
+    private void recreateAmbientHeader(Notification.Builder builder) {
+        RemoteViews header;
+        StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
+        if (builder == null) {
+            builder = Notification.Builder.recoverBuilder(getContext(),
+                    notification.getNotification());
+        }
+        header = builder.makeNotificationHeader(true /* ambient */);
+        if (mNotificationHeaderAmbient == null) {
+            mNotificationHeaderAmbient = (ViewGroup) header.apply(getContext(), this);
+            mNotificationHeaderWrapperAmbient = NotificationViewWrapper.wrap(getContext(),
+                    mNotificationHeaderAmbient, mContainingNotification);
+            mNotificationHeaderWrapperAmbient.notifyContentUpdated(mContainingNotification);
+            addView(mNotificationHeaderAmbient, 0);
+            invalidate();
+        } else {
+            header.reapply(getContext(), mNotificationHeaderAmbient);
+        }
+        resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, calculateDesiredHeader());
+        mNotificationHeaderWrapperAmbient.notifyContentUpdated(mContainingNotification);
+    }
+
     /**
      * Recreate the low-priority header.
      *
@@ -322,6 +354,7 @@
                 header.reapply(getContext(), mNotificationHeaderLowPriority);
             }
             mNotificationHeaderWrapperLowPriority.notifyContentUpdated(mContainingNotification);
+            resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
         } else {
             removeView(mNotificationHeaderLowPriority);
             mNotificationHeaderLowPriority = null;
@@ -339,10 +372,6 @@
         if (childCount > maxAllowedVisibleChildren) {
             mOverflowNumber = mHybridGroupManager.bindOverflowNumber(
                     mOverflowNumber, childCount - maxAllowedVisibleChildren);
-            if (mOverflowInvertHelper == null) {
-                mOverflowInvertHelper = new ViewInvertHelper(mOverflowNumber,
-                        NotificationPanelView.DOZE_ANIMATION_DURATION);
-            }
             if (mGroupOverFlowState == null) {
                 mGroupOverFlowState = new ViewState();
                 mNeverAppliedGroupState = true;
@@ -360,7 +389,6 @@
                 });
             }
             mOverflowNumber = null;
-            mOverflowInvertHelper = null;
             mGroupOverFlowState = null;
         }
     }
@@ -449,6 +477,7 @@
         if (mUserLocked) {
             expandFactor = getGroupExpandFraction();
         }
+        boolean childrenExpanded = mChildrenExpanded || mContainingNotification.isShowingAmbient();
         for (int i = 0; i < childCount; i++) {
             if (visibleChildren >= maxAllowedVisibleChildren) {
                 break;
@@ -458,7 +487,7 @@
                     intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
                             expandFactor);
                 } else {
-                    intrinsicHeight += mChildrenExpanded ? mDividerHeight : mChildPadding;
+                    intrinsicHeight += childrenExpanded ? mDividerHeight : mChildPadding;
                 }
             } else {
                 if (mUserLocked) {
@@ -467,7 +496,7 @@
                             mNotificatonTopPadding + mDividerHeight,
                             expandFactor);
                 } else {
-                    intrinsicHeight += mChildrenExpanded
+                    intrinsicHeight += childrenExpanded
                             ? mNotificatonTopPadding + mDividerHeight
                             : 0;
                 }
@@ -480,7 +509,7 @@
         if (mUserLocked) {
             intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
                     expandFactor);
-        } else if (!mChildrenExpanded) {
+        } else if (!childrenExpanded) {
             intrinsicHeight += mCollapsedBottompadding;
         }
         return intrinsicHeight;
@@ -515,7 +544,7 @@
                     yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
                             expandFactor);
                 } else {
-                    yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
+                    yPosition += childrenExpanded ? mDividerHeight : mChildPadding;
                 }
             } else {
                 if (expandingToExpandedGroup) {
@@ -524,7 +553,7 @@
                             mNotificatonTopPadding + mDividerHeight,
                             expandFactor);
                 } else {
-                    yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
+                    yPosition += childrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
                 }
                 firstChild = false;
             }
@@ -560,15 +589,21 @@
             ExpandableNotificationRow overflowView = mChildren.get(Math.min(
                     getMaxAllowedVisibleChildren(true /* likeCollpased */), childCount) - 1);
             mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView));
-            if (!mChildrenExpanded) {
-                if (mUserLocked) {
-                    HybridNotificationView singleLineView = overflowView.getSingleLineView();
-                    View mirrorView = singleLineView.getTextView();
+
+            if (mContainingNotification.isShowingAmbient() || !mChildrenExpanded) {
+                HybridNotificationView alignView = null;
+                if (mContainingNotification.isShowingAmbient()) {
+                    alignView = overflowView.getAmbientSingleLineView();
+                } else if (mUserLocked) {
+                    alignView = overflowView.getSingleLineView();
+                }
+                if (alignView != null) {
+                    View mirrorView = alignView.getTextView();
                     if (mirrorView.getVisibility() == GONE) {
-                        mirrorView = singleLineView.getTitleView();
+                        mirrorView = alignView.getTitleView();
                     }
                     if (mirrorView.getVisibility() == GONE) {
-                        mirrorView = singleLineView;
+                        mirrorView = alignView;
                     }
                     mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
                             mirrorView, overflowView);
@@ -620,6 +655,9 @@
     }
 
     private int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
+        if (mContainingNotification.isShowingAmbient()) {
+            return NUMBER_OF_CHILDREN_WHEN_AMBIENT;
+        }
         if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())) {
             return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
         }
@@ -794,40 +832,78 @@
         return mNotificationHeaderLowPriority;
     }
 
+    public void notifyShowAmbientChanged() {
+        updateHeaderVisibility(false);
+    }
+
     private void updateHeaderVisibility(boolean animate) {
-        NotificationHeaderView visibleHeader = mNotificationHeader;
-        NotificationHeaderView hiddenHeader = mNotificationHeaderLowPriority;
-        boolean normalHeaderVisible = true;
-        if (showingAsLowPriority()) {
-            visibleHeader = mNotificationHeaderLowPriority;
-            hiddenHeader = mNotificationHeader;
-            normalHeaderVisible = false;
+        ViewGroup desiredHeader;
+        ViewGroup currentHeader = mCurrentHeader;
+        desiredHeader = calculateDesiredHeader();
+
+        if (currentHeader == desiredHeader) {
+            return;
         }
+        if (desiredHeader == mNotificationHeaderAmbient
+                || currentHeader == mNotificationHeaderAmbient) {
+            animate = false;
+        }
+
         if (animate) {
-            if (visibleHeader != null && hiddenHeader != null
-                    && mShowingNormalHeader != normalHeaderVisible) {
-                hiddenHeader.setVisibility(VISIBLE);
-                visibleHeader.setVisibility(VISIBLE);
-                NotificationViewWrapper visibleWrapper = getWrapperForView(visibleHeader);
-                NotificationViewWrapper hiddenWrapper = getWrapperForView(hiddenHeader);
+            if (desiredHeader != null && currentHeader != null) {
+                currentHeader.setVisibility(VISIBLE);
+                desiredHeader.setVisibility(VISIBLE);
+                NotificationViewWrapper visibleWrapper = getWrapperForView(desiredHeader);
+                NotificationViewWrapper hiddenWrapper = getWrapperForView(currentHeader);
                 visibleWrapper.transformFrom(hiddenWrapper);
                 hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false));
-                startChildAlphaAnimations(normalHeaderVisible);
+                startChildAlphaAnimations(desiredHeader == mNotificationHeader);
             } else {
                 animate = false;
             }
         }
         if (!animate) {
-            if (visibleHeader != null) {
-                getWrapperForView(visibleHeader).setVisible(true);
-                visibleHeader.setVisibility(VISIBLE);
+            if (desiredHeader != null) {
+                getWrapperForView(desiredHeader).setVisible(true);
+                desiredHeader.setVisibility(VISIBLE);
             }
-            if (hiddenHeader != null) {
-                getWrapperForView(hiddenHeader).setVisible(false);
-                hiddenHeader.setVisibility(INVISIBLE);
+            if (currentHeader != null) {
+                getWrapperForView(currentHeader).setVisible(false);
+                currentHeader.setVisibility(INVISIBLE);
             }
         }
-        mShowingNormalHeader = normalHeaderVisible;
+
+        resetHeaderVisibilityIfNeeded(mNotificationHeader, desiredHeader);
+        resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, desiredHeader);
+        resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, desiredHeader);
+
+        mCurrentHeader = currentHeader;
+    }
+
+    private void resetHeaderVisibilityIfNeeded(View header, View desiredHeader) {
+        if (header == null) {
+            return;
+        }
+        if (header != mCurrentHeader && header != desiredHeader) {
+            getWrapperForView(header).setVisible(false);
+            header.setVisibility(INVISIBLE);
+        }
+        if (header == desiredHeader && header.getVisibility() != VISIBLE) {
+            getWrapperForView(header).setVisible(true);
+            header.setVisibility(VISIBLE);
+        }
+    }
+
+    private ViewGroup calculateDesiredHeader() {
+        ViewGroup desiredHeader;
+        if (mContainingNotification.isShowingAmbient()) {
+            desiredHeader = mNotificationHeaderAmbient;
+        } else if (showingAsLowPriority()) {
+            desiredHeader = mNotificationHeaderLowPriority;
+        } else {
+            desiredHeader = mNotificationHeader;
+        }
+        return desiredHeader;
     }
 
     private void startChildAlphaAnimations(boolean toVisible) {
@@ -861,10 +937,13 @@
 
     }
 
-    private NotificationViewWrapper getWrapperForView(NotificationHeaderView visibleHeader) {
+    private NotificationViewWrapper getWrapperForView(View visibleHeader) {
         if (visibleHeader == mNotificationHeader) {
             return mNotificationHeaderWrapper;
         }
+        if (visibleHeader == mNotificationHeaderAmbient) {
+            return mNotificationHeaderWrapperAmbient;
+        }
         return mNotificationHeaderWrapperLowPriority;
     }
 
@@ -971,7 +1050,9 @@
     }
 
     public int getMinHeight() {
-        return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
+        return getMinHeight(mContainingNotification.isShowingAmbient()
+                ? NUMBER_OF_CHILDREN_WHEN_AMBIENT
+                : NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
     }
 
     public int getCollapsedHeight() {
@@ -1016,7 +1097,7 @@
 
     public void setDark(boolean dark, boolean fade, long delay) {
         if (mOverflowNumber != null) {
-            mOverflowInvertHelper.setInverted(dark, fade, delay);
+            mHybridGroupManager.setOverflowNumberDark(mOverflowNumber, dark, fade, delay);
         }
         mNotificationHeaderWrapper.setDark(dark, fade, delay);
     }
@@ -1030,6 +1111,10 @@
             removeView(mNotificationHeaderLowPriority);
             mNotificationHeaderLowPriority = null;
         }
+        if (mNotificationHeaderAmbient != null) {
+            removeView(mNotificationHeaderAmbient);
+            mNotificationHeaderAmbient = null;
+        }
         recreateNotificationHeader(listener);
         initDimens();
         for (int i = 0; i < mDividers.size(); i++) {
@@ -1042,7 +1127,6 @@
         }
         removeView(mOverflowNumber);
         mOverflowNumber = null;
-        mOverflowInvertHelper = null;
         mGroupOverFlowState = null;
         updateGroupOverflow();
     }
@@ -1061,7 +1145,8 @@
 
     public void onNotificationUpdated() {
         mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
-                mContainingNotification.getNotificationColor());
+                mContainingNotification.getNotificationColor(),
+                mContainingNotification.getNotificationColorAmbient());
     }
 
     public int getPositionInLinearLayout(View childInGroup) {