Notification MessagingStyle: Add handset views

Bug: 27250207
Change-Id: I499cf2beaeeb34f0f189815fc0911f3b8954bd50
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a759719..520acf5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -29,6 +29,7 @@
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -43,6 +44,7 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.text.BidiFormatter;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -588,8 +590,8 @@
     private static final int COLOR_INVALID = 1;
 
     /**
-     * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 
-     * the notification's presence and contents in untrusted situations (namely, on the secure 
+     * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
+     * the notification's presence and contents in untrusted situations (namely, on the secure
      * lockscreen).
      *
      * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
@@ -2227,7 +2229,8 @@
                         Log.d(TAG, "Unknown style class: " + templateClass);
                     } else {
                         try {
-                            final Constructor<? extends Style> ctor = styleClass.getConstructor();
+                            final Constructor<? extends Style> ctor =
+                                    styleClass.getDeclaredConstructor();
                             ctor.setAccessible(true);
                             final Style style = ctor.newInstance();
                             style.restoreFromExtras(mN.extras);
@@ -3126,6 +3129,18 @@
          * @param hasProgress whether the progress bar should be shown and set
          */
         private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) {
+            final Bundle ex = mN.extras;
+
+            CharSequence title = processLegacyText(ex.getCharSequence(EXTRA_TITLE));
+            CharSequence text = processLegacyText(ex.getCharSequence(EXTRA_TEXT));
+            return applyStandardTemplate(resId, hasProgress, title, text);
+        }
+
+        /**
+         * @param hasProgress whether the progress bar should be shown and set
+         */
+        private RemoteViews applyStandardTemplate(int resId, boolean hasProgress,
+                CharSequence title, CharSequence text) {
             RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
 
             resetStandardTemplate(contentView);
@@ -3134,17 +3149,15 @@
 
             bindNotificationHeader(contentView);
             bindLargeIcon(contentView);
-            if (ex.getCharSequence(EXTRA_TITLE) != null) {
+            if (title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
-                contentView.setTextViewText(R.id.title,
-                        processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
+                contentView.setTextViewText(R.id.title, title);
             }
             boolean showProgress = handleProgressBar(hasProgress, contentView, ex);
-            if (ex.getCharSequence(EXTRA_TEXT) != null) {
+            if (text != null) {
                 int textId = showProgress ? com.android.internal.R.id.text_line_1
                         : com.android.internal.R.id.text;
-                contentView.setTextViewText(textId, processLegacyText(
-                        ex.getCharSequence(EXTRA_TEXT)));
+                contentView.setTextViewText(textId, text);
                 contentView.setViewVisibility(textId, View.VISIBLE);
             }
 
@@ -3749,6 +3762,10 @@
             return R.layout.notification_template_material_inbox;
         }
 
+        private int getMessagingLayoutResource() {
+            return R.layout.notification_template_material_messaging;
+        }
+
         private int getActionLayoutResource() {
             return R.layout.notification_material_action;
         }
@@ -4375,13 +4392,100 @@
         /**
          * @hide
          */
-        public RemoteViews makeBigContentView() {
-            // TODO handset to write implementation
-            RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
+        @Override
+        public RemoteViews makeContentView() {
+            Message m = findLatestIncomingMessage();
+            CharSequence title = mConversationTitle != null
+                    ? mConversationTitle
+                    : (m == null) ? null : m.mSender;
+            CharSequence text = (m == null)
+                    ? null
+                    : mConversationTitle != null ? makeMessageLine(m) : m.mText;
 
+            return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
+                    false /* hasProgress */,
+                    title,
+                    text);
+        }
+
+        private Message findLatestIncomingMessage() {
+            for (int i = mMessages.size() - 1; i >= 0; i--) {
+                Message m = mMessages.get(i);
+                // Incoming messages have a non-empty sender.
+                if (!TextUtils.isEmpty(m.mSender)) {
+                    return m;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public RemoteViews makeBigContentView() {
+            CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+                    ? super.mBigContentTitle
+                    : mConversationTitle;
+            boolean hasTitle = !TextUtils.isEmpty(title);
+
+            RemoteViews contentView = mBuilder.applyStandardTemplate(
+                    mBuilder.getMessagingLayoutResource(),
+                    false /* hasProgress */,
+                    title,
+                    null /* text */);
+
+            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
+                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
+
+            // Make sure all rows are gone in case we reuse a view.
+            for (int rowId : rowIds) {
+                contentView.setViewVisibility(rowId, View.GONE);
+            }
+
+            int i=0;
+            int titlePadding = mBuilder.mContext.getResources().getDimensionPixelSize(
+                    R.dimen.notification_messaging_spacing);
+            contentView.setViewLayoutMarginBottom(R.id.line1, hasTitle ? titlePadding : 0);
+            contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
+                    mBuilder.mN.mLargeIcon == null ? 0 : (hasTitle ? 1 : 2));
+
+            int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
+            while (firstMessage + i < mMessages.size() && i < rowIds.length) {
+                Message m = mMessages.get(firstMessage + i);
+                int rowId = rowIds[i];
+
+                contentView.setViewVisibility(rowId, View.VISIBLE);
+                contentView.setTextViewText(rowId, makeMessageLine(m));
+
+                i++;
+            }
             return contentView;
         }
 
+        private CharSequence makeMessageLine(Message m) {
+            BidiFormatter bidi = BidiFormatter.getInstance();
+            SpannableStringBuilder sb = new SpannableStringBuilder();
+            if (TextUtils.isEmpty(m.mSender)) {
+                CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
+                sb.append(bidi.unicodeWrap(replyName),
+                        makeFontColorSpan(mBuilder.resolveContrastColor()),
+                        0 /* flags */);
+            } else {
+                sb.append(bidi.unicodeWrap(m.mSender),
+                        makeFontColorSpan(Color.BLACK),
+                        0 /* flags */);
+            }
+            CharSequence text = m.mText == null ? "" : m.mText;
+            sb.append("  ").append(bidi.unicodeWrap(text));
+            return sb;
+        }
+
+        private static TextAppearanceSpan makeFontColorSpan(int color) {
+            return new TextAppearanceSpan(null, 0, 0,
+                    ColorStateList.valueOf(color), null);
+        }
+
         public static final class Message implements Parcelable {
 
             private final CharSequence mText;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 6d2cea6..a9b7f4e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1856,6 +1856,7 @@
         public static final int LAYOUT_MARGIN_END = 1;
         /** Set width */
         public static final int LAYOUT_WIDTH = 2;
+        public static final int LAYOUT_MARGIN_BOTTOM = 3;
 
         /**
          * @param viewId ID of the view alter
@@ -1898,6 +1899,12 @@
                         target.setLayoutParams(layoutParams);
                     }
                     break;
+                case LAYOUT_MARGIN_BOTTOM:
+                    if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
+                        ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = value;
+                        target.setLayoutParams(layoutParams);
+                    }
+                    break;
                 case LAYOUT_WIDTH:
                     layoutParams.width = value;
                     target.setLayoutParams(layoutParams);
@@ -2870,6 +2877,16 @@
     }
 
     /**
+     * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}.
+     *
+     * @hide
+     */
+    public void setViewLayoutMarginBottom(int viewId, int bottomMargin) {
+        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM,
+                bottomMargin));
+    }
+
+    /**
      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}.
      * @hide
      */
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 78c5e34..e2d8ffc 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -16,13 +16,18 @@
 
 package com.android.internal.widget;
 
+import com.android.internal.R;
+
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
 import android.text.BoringLayout;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.view.RemotableViewMethod;
 import android.widget.RemoteViews;
 import android.widget.TextView;
@@ -35,7 +40,8 @@
 @RemoteViews.RemoteView
 public class ImageFloatingTextView extends TextView {
 
-    private boolean mHasImage;
+    /** Number of lines from the top to indent */
+    private int mIndentLines;
 
     public ImageFloatingTextView(Context context) {
         this(context, null);
@@ -69,10 +75,16 @@
                 .setEllipsizedWidth(ellipsisWidth)
                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
-        // we set the endmargin on the first 2 lines. this works just in our case but that's
-        // sufficient for now.
-        int endMargin = (int) (getResources().getDisplayMetrics().density * 52);
-        int[] margins = mHasImage ? new int[] {endMargin, endMargin, 0} : null;
+        // we set the endmargin on the requested number of lines.
+        int endMargin = getContext().getResources().getDimensionPixelSize(
+                R.dimen.notification_content_picture_margin);
+        int[] margins = null;
+        if (mIndentLines > 0) {
+            margins = new int[mIndentLines + 1];
+            for (int i = 0; i < mIndentLines; i++) {
+                margins[i] = endMargin;
+            }
+        }
         if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
             builder.setIndents(margins, null);
         } else {
@@ -84,8 +96,22 @@
 
     @RemotableViewMethod
     public void setHasImage(boolean hasImage) {
-        mHasImage = hasImage;
+        mIndentLines = hasImage ? 2 : 0;
         // The new layout will be automatically created when the text is
         // set again by the notification.
     }
+
+    /**
+     * @param lines the number of lines at the top that should be indented by indentEnd
+     * @return whether a change was made
+     */
+    public boolean setNumIndentLines(int lines) {
+        if (mIndentLines != lines) {
+            mIndentLines = lines;
+            // Invalidate layout.
+            setHint(getHint());
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
new file mode 100644
index 0000000..dc7b7f5
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2016 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.internal.widget;
+
+import com.android.internal.R;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle.
+ *
+ * Evicts children until they all fit.
+ */
+@RemoteViews.RemoteView
+public class MessagingLinearLayout extends ViewGroup {
+
+    /**
+     * Spacing to be applied between views.
+     */
+    private int mSpacing;
+
+    /**
+     * The maximum height allowed.
+     */
+    private int mMaxHeight;
+
+    private int mIndentLines;
+
+    public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.MessagingLinearLayout, 0,
+                0);
+
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.MessagingLinearLayout_maxHeight:
+                    mMaxHeight = a.getDimensionPixelSize(i, 0);
+                    break;
+                case R.styleable.MessagingLinearLayout_spacing:
+                    mSpacing = a.getDimensionPixelSize(i, 0);
+                    break;
+            }
+        }
+
+        if (mMaxHeight <= 0) {
+            throw new IllegalStateException(
+                    "MessagingLinearLayout: Must specify positive maxHeight");
+        }
+
+        a.recycle();
+    }
+
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // This is essentially a bottom-up linear layout that only adds children that fit entirely
+        // up to a maximum height.
+
+        switch (MeasureSpec.getMode(heightMeasureSpec)) {
+            case MeasureSpec.AT_MOST:
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        Math.min(mMaxHeight, MeasureSpec.getSize(heightMeasureSpec)),
+                        MeasureSpec.AT_MOST);
+                break;
+            case MeasureSpec.UNSPECIFIED:
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        mMaxHeight,
+                        MeasureSpec.AT_MOST);
+                break;
+            case MeasureSpec.EXACTLY:
+                break;
+        }
+        final int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
+        final int count = getChildCount();
+
+        for (int i = 0; i < count; ++i) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            lp.hide = true;
+        }
+
+        int totalHeight = mPaddingTop + mPaddingBottom;
+        boolean first = true;
+
+        // Starting from the bottom: we measure every view as if it were the only one. If it still
+        // fits, we take it, otherwise we stop there.
+        for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
+            if (getChildAt(i).getVisibility() == GONE) {
+                continue;
+            }
+            final View child = getChildAt(i);
+            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+
+            if (child instanceof ImageFloatingTextView) {
+                // Pretend we need the image padding for all views, we don't know which
+                // one will end up needing to do this (might end up not using all the space,
+                // but calculating this exactly would be more expensive).
+                ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines);
+            }
+
+            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+
+            final int childHeight = child.getMeasuredHeight();
+            int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
+                    lp.bottomMargin + (first ? 0 : mSpacing));
+            first = false;
+
+            if (newHeight <= targetHeight) {
+                totalHeight = newHeight;
+                lp.hide = false;
+            } else {
+                break;
+            }
+        }
+
+        // Now that we know which views to take, fix up the indents and see what width we get.
+        int measuredWidth = mPaddingLeft + mPaddingRight;
+        int imageLines = mIndentLines;
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            if (child.getVisibility() == GONE || lp.hide) {
+                continue;
+            }
+
+            if (child instanceof ImageFloatingTextView) {
+                ImageFloatingTextView textChild = (ImageFloatingTextView) child;
+                if (imageLines == 2 && textChild.getLineCount() > 2) {
+                    // HACK: If we need indent for two lines, and they're coming from the same
+                    // view, we need extra spacing to compensate for the lack of margins,
+                    // so add an extra line of indent.
+                    imageLines = 3;
+                }
+                boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
+                if (changed) {
+                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+                }
+                imageLines -= textChild.getLineCount();
+            }
+
+            measuredWidth = Math.max(measuredWidth,
+                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+                            + mPaddingLeft + mPaddingRight);
+        }
+
+
+        setMeasuredDimension(
+                resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
+                        widthMeasureSpec),
+                resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
+                        heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int paddingLeft = mPaddingLeft;
+
+        int childTop;
+
+        // Where right end of child should go
+        final int width = right - left;
+        final int childRight = width - mPaddingRight;
+
+        final int layoutDirection = getLayoutDirection();
+        final int count = getChildCount();
+
+        childTop = mPaddingTop;
+
+        boolean first = true;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            if (child.getVisibility() == GONE || lp.hide) {
+                continue;
+            }
+
+            final int childWidth = child.getMeasuredWidth();
+            final int childHeight = child.getMeasuredHeight();
+
+            int childLeft;
+            if (layoutDirection == LAYOUT_DIRECTION_RTL) {
+                childLeft = childRight - childWidth - lp.rightMargin;
+            } else {
+                childLeft = paddingLeft + lp.leftMargin;
+            }
+
+            if (!first) {
+                childTop += mSpacing;
+            }
+
+            childTop += lp.topMargin;
+            child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
+
+            childTop += childHeight + lp.bottomMargin;
+
+            first = false;
+        }
+    }
+
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (lp.hide) {
+            return true;
+        }
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(mContext, attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        LayoutParams copy = new LayoutParams(lp.width, lp.height);
+        if (lp instanceof MarginLayoutParams) {
+            copy.copyMarginsFrom((MarginLayoutParams) lp);
+        }
+        return copy;
+    }
+
+    @RemotableViewMethod
+    /**
+     * Sets how many lines should be indented to avoid a floating image.
+     */
+    public void setNumIndentLines(int numberLines) {
+        mIndentLines = numberLines;
+    }
+
+    public static class LayoutParams extends MarginLayoutParams {
+
+        boolean hide = false;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+    }
+}
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
new file mode 100644
index 0000000..7d718e0
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:tag="messaging"
+    >
+    <include layout="@layout/notification_template_header" />
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="top"
+            android:layout_marginTop="@dimen/notification_content_margin_top"
+            android:clipToPadding="false"
+            android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:paddingStart="@dimen/notification_content_margin_start"
+            android:paddingEnd="@dimen/notification_content_margin_end"
+            android:minHeight="@dimen/notification_min_content_height"
+            android:clipToPadding="false"
+            android:orientation="vertical"
+            >
+            <include layout="@layout/notification_template_part_line1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <com.android.internal.widget.MessagingLinearLayout
+                android:id="@+id/notification_messaging"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingBottom="@dimen/notification_content_margin_bottom"
+                android:spacing="@dimen/notification_messaging_spacing"
+                android:maxHeight="212dp">
+                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
+                    style="@style/Widget.Material.Notification.MessagingText"
+                    />
+                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1"
+                    style="@style/Widget.Material.Notification.MessagingText"
+                    />
+                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2"
+                    style="@style/Widget.Material.Notification.MessagingText"
+                    />
+                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3"
+                    style="@style/Widget.Material.Notification.MessagingText"
+                    />
+                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4"
+                    style="@style/Widget.Material.Notification.MessagingText"
+                    />
+                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5"
+                    style="@style/Widget.Material.Notification.MessagingText"
+                    />
+                <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6"
+                    style="@style/Widget.Material.Notification.MessagingText"
+                    />
+            </com.android.internal.widget.MessagingLinearLayout>
+        </LinearLayout>
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
+    <include layout="@layout/notification_template_right_icon" />
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index 15ccc67..b652127 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -16,9 +16,9 @@
   -->
 
 <ImageView android:id="@+id/right_icon" xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="40dp"
-    android:layout_height="40dp"
-    android:layout_marginEnd="16dp"
+    android:layout_width="@dimen/notification_large_icon_width"
+    android:layout_height="@dimen/notification_large_icon_width"
+    android:layout_marginEnd="@dimen/notification_content_margin_end"
     android:layout_marginTop="36dp"
     android:layout_gravity="top|end"
     android:scaleType="centerCrop"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0ed1f13..a320ef6 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8129,6 +8129,11 @@
         <attr name="maxCollapsedHeightSmall" format="dimension" />
     </declare-styleable>
 
+    <declare-styleable name="MessagingLinearLayout">
+        <attr name="maxHeight" />
+        <attr name="spacing" />
+    </declare-styleable>
+
     <declare-styleable name="ResolverDrawerLayout_LayoutParams">
         <attr name="layout_alwaysShow" format="boolean" />
         <attr name="layout_ignoreOffset" format="boolean" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index dd54d57..9178305 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -162,9 +162,9 @@
     <dimen name="notification_min_height">92dp</dimen>
 
     <!-- The width of the big icons in notifications. -->
-    <dimen name="notification_large_icon_width">64dp</dimen>
+    <dimen name="notification_large_icon_width">40dp</dimen>
     <!-- The width of the big icons in notifications. -->
-    <dimen name="notification_large_icon_height">64dp</dimen>
+    <dimen name="notification_large_icon_height">40dp</dimen>
 
     <!-- The minimum width of the app name in the header if it shrinks -->
     <dimen name="notification_header_shrink_min_width">72dp</dimen>
@@ -181,6 +181,9 @@
     <!-- The margin of the content to an image-->
     <dimen name="notification_content_image_margin_end">8dp</dimen>
 
+    <!-- The spacing between messages in Notification.MessagingStyle -->
+    <dimen name="notification_messaging_spacing">6dp</dimen>
+
     <!-- Preferred width of the search view. -->
     <dimen name="search_view_preferred_width">320dip</dimen>
 
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 2420c1a..8a33406 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -456,6 +456,14 @@
 
     <style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" />
 
+    <style name="Widget.Material.Notification.MessagingText" parent="Widget.Material.Light.TextView">
+        <item name="layout_width">match_parent</item>
+        <item name="layout_height">wrap_content</item>
+        <item name="ellipsize">end</item>
+        <item name="visibility">gone</item>
+        <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+    </style>
+
     <!-- Widget Styles -->
 
     <style name="Material"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7eaff7b..03d2192 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2494,6 +2494,8 @@
   <java-symbol type="bool" name="config_strongAuthRequiredOnBoot" />
 
   <java-symbol type="layout" name="app_anr_dialog" />
+  <java-symbol type="layout" name="notification_template_material_messaging" />
+
   <java-symbol type="id" name="aerr_wait" />
 
   <java-symbol type="id" name="notification_content_container" />
@@ -2523,12 +2525,16 @@
   <java-symbol type="string" name="carrier_app_notification_text" />
   <java-symbol type="string" name="negative_duration" />
 
+  <java-symbol type="dimen" name="notification_messaging_spacing" />
+
   <!-- WallpaperManager config -->
   <java-symbol type="string" name="config_wallpaperCropperPackage" />
 
   <java-symbol type="id" name="textSpacerNoTitle" />
   <java-symbol type="id" name="titleDividerNoCustom" />
 
+  <java-symbol type="id" name="notification_messaging" />
+
   <java-symbol type="bool" name="config_sustainedPerformanceModeSupported" />
 
   <!-- Wearable input extract edit view -->