Ad teaser redesign.

b/12243411.

Change-Id: I993b326bc36de8edf1f2f40202d8f32551de4d94
diff --git a/res/layout/conversation_item_view_normal.xml b/res/layout/conversation_item_view_normal.xml
index b34a019..f1f24ae 100644
--- a/res/layout/conversation_item_view_normal.xml
+++ b/res/layout/conversation_item_view_normal.xml
@@ -29,10 +29,11 @@
     android:orientation="vertical">
 
     <LinearLayout
+        android:id="@+id/conversation_item_frame"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginLeft="16dp"
-        android:layout_marginRight="16dp"
+        android:layout_marginLeft="@dimen/conv_list_padding"
+        android:layout_marginRight="@dimen/conv_list_padding"
         android:orientation="horizontal">
 
         <View
diff --git a/res/layout/conversation_item_view_normal_spacious.xml b/res/layout/conversation_item_view_normal_spacious.xml
index be76c11..c93d41d 100644
--- a/res/layout/conversation_item_view_normal_spacious.xml
+++ b/res/layout/conversation_item_view_normal_spacious.xml
@@ -29,10 +29,11 @@
     android:orientation="vertical">
 
     <LinearLayout
+        android:id="@+id/conversation_item_frame"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
-        style="@style/ConversationListSpaciousLinearLayoutStyle" >
+        style="@style/ConversationListSpaciousStyle" >
 
         <View
             android:id="@+id/contact_image"
diff --git a/res/layout/conversation_item_view_wide.xml b/res/layout/conversation_item_view_wide.xml
index 3e36b52..adc6f4c 100644
--- a/res/layout/conversation_item_view_wide.xml
+++ b/res/layout/conversation_item_view_wide.xml
@@ -29,9 +29,10 @@
     android:orientation="vertical">
 
     <FrameLayout
+        android:id="@+id/conversation_item_frame"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        style="@style/ConversationListFrameStyle" >
+        style="@style/ConversationListWideStyle" >
 
         <!-- minHeight here is to ensure more consistent item heights across gadget choices -->
         <!-- and between 'normal' vs. 'wide' layouts (which is important during 2-pane -->
@@ -70,15 +71,27 @@
                     android:src="@drawable/ic_badge_reply_holo_light"
                     style="@style/ConversationListReplyStateStyle" />
 
-                <TextView
-                    android:id="@+id/senders"
+                <LinearLayout
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_weight="1"
-                    android:textSize="18sp"
-                    android:lines="1"
-                    android:includeFontPadding="false"
-                    android:text="@string/long_string" />
+                    android:orientation="vertical" >
+                    <TextView
+                        android:id="@+id/senders"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:textSize="18sp"
+                        android:lines="1"
+                        android:includeFontPadding="false"
+                        android:text="@string/long_string" />
+                    <TextView
+                        android:id="@+id/badge"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:lines="1"
+                        android:includeFontPadding="false"
+                        android:text="@string/long_string" />
+                </LinearLayout>
 
                 <FrameLayout
                     android:layout_width="54dp"
diff --git a/res/values-ldrtl/styles-ldrtl.xml b/res/values-ldrtl/styles-ldrtl.xml
index 0f350b5..c931b7f 100644
--- a/res/values-ldrtl/styles-ldrtl.xml
+++ b/res/values-ldrtl/styles-ldrtl.xml
@@ -324,9 +324,9 @@
         <item name="android:layout_marginEnd">@dimen/ap_margin_side</item>
     </style>
 
-    <style name="ConversationListFrameStyle">
-        <item name="android:layout_marginStart">@dimen/conv_list_frame_padding_start</item>
-        <item name="android:layout_marginEnd">@dimen/conv_list_frame_padding_end</item>
+    <style name="ConversationListWideStyle">
+        <item name="android:layout_marginStart">@dimen/conv_list_wide_padding_start</item>
+        <item name="android:layout_marginEnd">@dimen/conv_list_wide_padding_end</item>
     </style>
 
     <style name="ConversationListWideContactImageStyle">
@@ -361,11 +361,9 @@
         <item name="android:layout_gravity">top|end</item>
     </style>
 
-    <style name="ConversationListSpaciousLinearLayoutStyle">
-        <item name="android:layout_marginStart">
-            @dimen/conv_list_spacious_linearlayout_padding_start</item>
-        <item name="android:layout_marginEnd">
-            @dimen/conv_list_spacious_linearlayout_padding_end</item>
+    <style name="ConversationListSpaciousStyle">
+        <item name="android:layout_marginStart">@dimen/conv_list_spacious_padding_start</item>
+        <item name="android:layout_marginEnd">@dimen/conv_list_spacious_padding_end</item>
     </style>
 
     <style name="ConversationListSpaciousContactImageStyle">
diff --git a/res/values/colors.xml b/res/values/colors.xml
index c095b6c..c44fd3a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -130,4 +130,6 @@
     <color name="swipe_refresh_color2">#ffdb4437</color>
     <color name="swipe_refresh_color3">#ff4285f4</color>
     <color name="swipe_refresh_color4">#fff4b400</color>
+
+    <color name="badge_background_color">#edb802</color>
 </resources>
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 801d003..d121151 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -160,6 +160,9 @@
     <dimen name="search_results_padding">8dip</dimen>
 
     <!-- conversation list dimensions -->
+    <dimen name="conv_list_padding">16dip</dimen>
+    <dimen name="conv_list_card_border_padding">8dip</dimen>
+    <dimen name="conv_list_no_border_padding">0dip</dimen>
     <dimen name="conv_list_contact_image_padding_end">12dip</dimen>
     <dimen name="conv_list_reply_state_padding_end">8dip</dimen>
     <dimen name="conv_list_personal_indicator_padding_start">-2dip</dimen>
@@ -169,16 +172,19 @@
     <dimen name="conv_list_paperclip_padding_start">6dip</dimen>
     <dimen name="conv_list_star_padding_end">-9dip</dimen>
 
-    <dimen name="conv_list_frame_padding_start">20dip</dimen>
-    <dimen name="conv_list_frame_padding_end">32dip</dimen>
+    <dimen name="conv_list_wide_padding_start">20dip</dimen>
+    <dimen name="conv_list_wide_padding_end">32dip</dimen>
     <dimen name="conv_list_wide_contact_image_padding_end">20dip</dimen>
     <dimen name="conv_list_wide_personal_indicator_padding_end">8dip</dimen>
     <dimen name="conv_list_wide_star_padding_start">32dip</dimen>
     <dimen name="conv_list_wide_color_block_padding_end">64dip</dimen>
 
-    <dimen name="conv_list_spacious_linearlayout_padding_start">22dip</dimen>
-    <dimen name="conv_list_spacious_linearlayout_padding_end">16dip</dimen>
+    <dimen name="conv_list_spacious_padding_start">22dip</dimen>
+    <dimen name="conv_list_spacious_padding_end">16dip</dimen>
     <dimen name="conv_list_spacious_contact_image_padding_end">16dip</dimen>
     <dimen name="conv_list_spacious_star_padding_end">-8dip</dimen>
 
+    <dimen name="badge_padding_extra_width">6dip</dimen>
+    <dimen name="badge_rounded_corner_radius">2dip</dimen>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c1b7f39..f7fc403 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -299,9 +299,10 @@
     <!-- Formatting string. If the subject contains the tag of a mailing-list (text surrounded with
     return the subject with that tag ellipsized, e.g. "[android-gmail-team] Hello" -> "[andr...] Hello" [CHAR LIMIT=100] -->
     <string name="filtered_tag"> [<xliff:g id="tag">%1$s</xliff:g>]<xliff:g id="subject">%2$s</xliff:g></string>
-    <!-- Displayed in Conversation Header View and Widget in the form of "subject - snippet"
-         [CHAR LIMIT=5] -->
+    <!-- Displayed in conversation list item in the form of "subject - snippet" [CHAR LIMIT=5] -->
     <string name="subject_and_snippet"><xliff:g>%s</xliff:g> \u2014 <xliff:g>%s</xliff:g></string>
+    <!-- Displayed in conversation list item in the form of "badge subject - snippet" [CHAR LIMIT=7] -->
+    <string name="badge_subject_and_snippet"><xliff:g>%1$s</xliff:g> <xliff:g>%2$s</xliff:g> \u2014 <xliff:g>%3$s</xliff:g></string>
     <!-- Displayed in browse list item when the list item is a draft message instead of showing the subject [CHAR LIMIT=100] -->
     <plurals name="draft">
         <!-- Title of the screen when there is exactly one draft -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 89c3060..b537b92 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -861,9 +861,9 @@
         <item name="android:layout_marginRight">@dimen/ap_margin_side</item>
     </style>
 
-    <style name="ConversationListFrameStyle">
-        <item name="android:layout_marginLeft">@dimen/conv_list_frame_padding_start</item>
-        <item name="android:layout_marginRight">@dimen/conv_list_frame_padding_end</item>
+    <style name="ConversationListWideStyle">
+        <item name="android:layout_marginLeft">@dimen/conv_list_wide_padding_start</item>
+        <item name="android:layout_marginRight">@dimen/conv_list_wide_padding_end</item>
     </style>
 
     <style name="ConversationListWideContactImageStyle">
@@ -898,11 +898,9 @@
         <item name="android:layout_gravity">top|right</item>
     </style>
 
-    <style name="ConversationListSpaciousLinearLayoutStyle">
-        <item name="android:layout_marginLeft">
-            @dimen/conv_list_spacious_linearlayout_padding_start</item>
-        <item name="android:layout_marginRight">
-            @dimen/conv_list_spacious_linearlayout_padding_end</item>
+    <style name="ConversationListSpaciousStyle">
+        <item name="android:layout_marginLeft">@dimen/conv_list_spacious_padding_start</item>
+        <item name="android:layout_marginRight">@dimen/conv_list_spacious_padding_end</item>
     </style>
 
     <style name="ConversationListSpaciousContactImageStyle">
@@ -915,4 +913,10 @@
     </style>
     <!-- END Conversation list styles -->
 
+    <style name="BadgeTextStyle">
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
 </resources>
diff --git a/src/com/android/mail/browse/BadgeSpan.java b/src/com/android/mail/browse/BadgeSpan.java
new file mode 100644
index 0000000..e849f7d
--- /dev/null
+++ b/src/com/android/mail/browse/BadgeSpan.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2014 Google Inc.
+ * Licensed to 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.mail.browse;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.RectF;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+import android.text.style.ReplacementSpan;
+
+/**
+ * A replacement span to use when displaying a badge in a conversation list item.
+ * A badge will be some piece of text with a colored background and rounded
+ * corners.
+ */
+public class BadgeSpan extends ReplacementSpan {
+
+    public interface BadgeSpanDimensions {
+        /**
+         * Returns the padding value that corresponds to one side of the
+         * horizontal padding surrounding the text inside the background color.
+         */
+        int getHorizontalPadding();
+
+        /**
+         * Returns the radius to use for the rounded corners on the background rect.
+         */
+        float getRoundedCornerRadius();
+    }
+
+    private TextPaint mWorkPaint = new TextPaint();
+    /**
+     * A reference to the enclosing Spanned object to collect other CharacterStyle spans and take
+     * them into account when drawing.
+     */
+    private final Spanned mSpanned;
+    private final BadgeSpanDimensions mDims;
+
+    public BadgeSpan(Spanned spanned, BadgeSpanDimensions dims) {
+        mSpanned = spanned;
+        mDims = dims;
+    }
+
+    @Override
+    public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
+        if (fm != null) {
+            paint.getFontMetricsInt(fm);
+            // The magic set of measurements to vertically center text within a colored region!
+            fm.top = fm.ascent;
+        }
+        return measureWidth(paint, text, start, end);
+    }
+
+    private int measureWidth(Paint paint, CharSequence text, int start, int end) {
+        return (int) paint.measureText(text, start, end) + 2 * mDims.getHorizontalPadding();
+    }
+
+    @Override
+    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
+            int y, int bottom, Paint paint) {
+
+        mWorkPaint.set(paint);
+
+        // take into account the foreground/background color spans when painting
+        final CharacterStyle[] otherSpans = mSpanned.getSpans(start, end, CharacterStyle.class);
+        for (CharacterStyle otherSpan : otherSpans) {
+            otherSpan.updateDrawState(mWorkPaint);
+        }
+
+        // paint a background if present
+        if (mWorkPaint.bgColor != 0) {
+            final int prevColor = mWorkPaint.getColor();
+            final Paint.Style prevStyle = mWorkPaint.getStyle();
+
+            mWorkPaint.setColor(mWorkPaint.bgColor);
+            mWorkPaint.setStyle(Paint.Style.FILL);
+
+            final int bgWidth = measureWidth(mWorkPaint, text, start, end);
+            final RectF rect = new RectF(x, top, x + bgWidth, bottom);
+            final float radius = mDims.getRoundedCornerRadius();
+            canvas.drawRoundRect(rect, radius, radius, mWorkPaint);
+
+            mWorkPaint.setColor(prevColor);
+            mWorkPaint.setStyle(prevStyle);
+        }
+
+        canvas.drawText(text, start, end, x + mDims.getHorizontalPadding(), y, mWorkPaint);
+    }
+}
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 2de6b15..ac991a0 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -35,6 +35,7 @@
 import android.graphics.Shader;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
 import android.support.v4.view.ViewCompat;
 import android.text.Layout.Alignment;
 import android.text.Spannable;
@@ -45,6 +46,7 @@
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.text.format.DateUtils;
+import android.text.style.BackgroundColorSpan;
 import android.text.style.CharacterStyle;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.TextAppearanceSpan;
@@ -61,8 +63,6 @@
 import android.widget.TextView;
 
 import com.android.mail.R;
-import com.android.mail.R.drawable;
-import com.android.mail.R.integer;
 import com.android.mail.analytics.Analytics;
 import com.android.mail.bitmap.AttachmentDrawable;
 import com.android.mail.bitmap.AttachmentGridDrawable;
@@ -100,7 +100,7 @@
 
 public class ConversationItemView extends View
         implements SwipeableItemView, ToggleableItem, InvalidateCallback, OnScrollListener,
-        ConversationSetObserver {
+        ConversationSetObserver, BadgeSpan.BadgeSpanDimensions {
 
     // Timer.
     private static int sLayoutCount = 0;
@@ -143,8 +143,9 @@
     private static int sSenderImageTouchSlop;
     private static int sShrinkAnimationDuration;
     private static int sSlideAnimationDuration;
-    private static int sOverflowCountMax;
     private static int sCabAnimationDuration;
+    private static int sBadgePaddingExtraWidth;
+    private static int sBadgeRoundedCornerRadius;
 
     // Static paints.
     private static final TextPaint sPaint = new TextPaint();
@@ -195,6 +196,7 @@
     private ControllableActivity mActivity;
     private final TextView mSubjectTextView;
     private final TextView mSendersTextView;
+    private final TextView mBadgeTextView;
     private int mGadgetMode;
     private boolean mAttachmentPreviewsEnabled;
     private boolean mParallaxSpeedAlternative;
@@ -203,6 +205,8 @@
     private static int sFoldersStartPadding;
     private static TextAppearanceSpan sSubjectTextUnreadSpan;
     private static TextAppearanceSpan sSubjectTextReadSpan;
+    private static TextAppearanceSpan sBadgeTextSpan;
+    private static BackgroundColorSpan sBadgeBackgroundSpan;
     private static ForegroundColorSpan sSnippetTextUnreadSpan;
     private static ForegroundColorSpan sSnippetTextReadSpan;
     private static int sScrollSlop;
@@ -354,8 +358,9 @@
                 if (labelTooLong) {
                     // todo - take RTL into account for fade
                     final int rightBorder = xLeft + width - sFoldersStartPadding - padding;
-                    final Shader shader = new LinearGradient(rightBorder - padding, y, rightBorder, y,
-                            fgColor, Utils.getTransparentColor(fgColor), Shader.TileMode.CLAMP);
+                    final Shader shader = new LinearGradient(rightBorder - padding, y,
+                            rightBorder, y, fgColor, Utils.getTransparentColor(fgColor),
+                            Shader.TileMode.CLAMP);
                     sFoldersPaint.setShader(shader);
                 }
                 canvas.drawText(folderString, xLeft + padding, y + height - textBottomPadding,
@@ -407,8 +412,8 @@
                     BitmapFactory.decodeResource(res, R.drawable.ic_badge_invite_holo_light);
             VISIBLE_CONVERSATION_CARET = BitmapFactory.decodeResource(res, R.drawable.caret_grey);
             RIGHT_EDGE_TABLET = res.getDrawable(R.drawable.list_edge_tablet);
-            PLACEHOLDER = res.getDrawable(drawable.ic_attachment_load);
-            PROGRESS_BAR = res.getDrawable(drawable.progress_holo);
+            PLACEHOLDER = res.getDrawable(R.drawable.ic_attachment_load);
+            PROGRESS_BAR = res.getDrawable(R.drawable.progress_holo);
 
             // Initialize colors.
             sActivatedTextSpan = CharacterStyle.wrap(new ForegroundColorSpan(
@@ -417,8 +422,11 @@
             sSendersTextColorUnread = res.getColor(R.color.senders_text_color_unread);
             sSubjectTextUnreadSpan = new TextAppearanceSpan(mContext,
                     R.style.SubjectAppearanceUnreadStyle);
-            sSubjectTextReadSpan = new TextAppearanceSpan(mContext,
-                    R.style.SubjectAppearanceReadStyle);
+            sBadgeTextSpan = new TextAppearanceSpan(mContext, R.style.BadgeTextStyle);
+            sBadgeBackgroundSpan = new BackgroundColorSpan(
+                    res.getColor(R.color.badge_background_color));
+            sSubjectTextReadSpan = new TextAppearanceSpan(
+                    mContext, R.style.SubjectAppearanceReadStyle);
             sSnippetTextUnreadSpan =
                     new ForegroundColorSpan(res.getColor(R.color.snippet_text_color_unread));
             sSnippetTextReadSpan =
@@ -433,8 +441,10 @@
             sElidedPaddingToken = res.getString(R.string.elided_padding_token);
             sScrollSlop = res.getInteger(R.integer.swipeScrollSlop);
             sFoldersStartPadding = res.getDimensionPixelOffset(R.dimen.folders_start_padding);
-            sOverflowCountMax = res.getInteger(integer.ap_overflow_max_count);
             sCabAnimationDuration = res.getInteger(R.integer.conv_item_view_cab_anim_duration);
+            sBadgePaddingExtraWidth = res.getDimensionPixelSize(R.dimen.badge_padding_extra_width);
+            sBadgeRoundedCornerRadius =
+                    res.getDimensionPixelSize(R.dimen.badge_rounded_corner_radius);
         }
 
         mSendersTextView = new TextView(mContext);
@@ -444,6 +454,9 @@
         mSubjectTextView.setEllipsize(TextUtils.TruncateAt.END);
         mSubjectTextView.setIncludeFontPadding(false);
 
+        mBadgeTextView = new TextView(mContext);
+        mBadgeTextView.setIncludeFontPadding(false);
+
         mAttachmentsView = new AttachmentGridDrawable(res, PLACEHOLDER, PROGRESS_BAR);
         mAttachmentsView.setCallback(this);
 
@@ -464,7 +477,8 @@
                 null /* conversationItemAreaClickListener */,
                 set, folder, checkboxOrSenderImage, showAttachmentPreviews,
                 parallaxSpeedAlternative, parallaxDirectionAlternative, swipeEnabled,
-                priorityArrowEnabled, adapter, -1 /* backgroundOverrideResId */, null /* photoBitmap */);
+                priorityArrowEnabled, adapter, -1 /* backgroundOverrideResId */,
+                null /* photoBitmap */, false /* useFullMargins */);
         Utils.traceEndSection();
     }
 
@@ -478,7 +492,7 @@
                 folder, checkboxOrSenderImage, false /* attachment previews */,
                 false /* parallax */, false /* parallax */, true /* swipeEnabled */,
                 false /* priorityArrowEnabled */,
-                adapter, backgroundOverrideResId, photoBitmap);
+                adapter, backgroundOverrideResId, photoBitmap, true /* useFullMargins */);
         Utils.traceEndSection();
     }
 
@@ -488,7 +502,8 @@
             final int checkboxOrSenderImage, final boolean showAttachmentPreviews,
             final boolean parallaxSpeedAlternative, final boolean parallaxDirectionAlternative,
             boolean swipeEnabled, final boolean priorityArrowEnabled, final AnimatedAdapter adapter,
-            final int backgroundOverrideResId, final Bitmap photoBitmap) {
+            final int backgroundOverrideResId, final Bitmap photoBitmap,
+            final boolean useFullMargins) {
         mBackgroundOverrideResId = backgroundOverrideResId;
         mPhotoBitmap = photoBitmap;
         mConversationItemAreaClickListener = conversationItemAreaClickListener;
@@ -580,19 +595,20 @@
                 mDisplayedFolder.folderUri, ignoreFolderType);
         Utils.traceEndSection();
 
-        if (mHeader.dateOverrideText == null) {
+        if (mHeader.showDateText) {
             Utils.traceBeginSection("relative time");
             mHeader.dateText = DateUtils.getRelativeTimeSpanString(mContext,
                     mHeader.conversation.dateMs);
             Utils.traceEndSection();
         } else {
-            mHeader.dateText = mHeader.dateOverrideText;
+            mHeader.dateText = "";
         }
 
         Utils.traceBeginSection("config setup");
         mConfig = new ConversationItemViewCoordinates.Config()
             .withGadget(mGadgetMode)
-            .withAttachmentPreviews(getAttachmentPreviewsMode());
+            .withAttachmentPreviews(getAttachmentPreviewsMode())
+            .setUseFullMargins(useFullMargins);
         if (header.folderDisplayer.hasVisibleFolders()) {
             mConfig.showFolders();
         }
@@ -736,6 +752,8 @@
         Utils.traceBeginSection("subject");
         createSubject(mHeader.unread);
 
+        createBadge();
+
         if (!mHeader.isLayoutValid()) {
             setContentDescription();
         }
@@ -764,6 +782,10 @@
         Drawable drawable = mBackgrounds.get(resourceId);
         if (drawable == null) {
             drawable = getResources().getDrawable(resourceId);
+            final int insetPadding = mHeader.insetPadding;
+            if (insetPadding > 0) {
+                drawable = new InsetDrawable(drawable, insetPadding);
+            }
             mBackgrounds.put(resourceId, drawable);
         }
         if (getBackground() != drawable) {
@@ -781,8 +803,7 @@
         setSelected(mSelected);
         mHeader.gadgetMode = mGadgetMode;
 
-        final boolean isUnread = mHeader.unread;
-        updateBackground(isUnread);
+        updateBackground();
 
         mHeader.sendersDisplayText = new SpannableStringBuilder();
         mHeader.styledSendersString = null;
@@ -1013,18 +1034,25 @@
     }
 
     private void createSubject(final boolean isUnread) {
+        // Need to check if we're in wide mode because the badge
+        // does not get added if we're in wide mode.
+        final String badgeText = mCoordinates.isWideMode() ? "" : mHeader.badgeText;
         final String subject = filterTag(mHeader.conversation.subject);
         final String snippet = mHeader.conversation.getSnippet();
         final Spannable displayedStringBuilder = new SpannableString(
-                Conversation.getSubjectAndSnippetForDisplay(mContext, subject, snippet));
+                Conversation.getSubjectAndSnippetForDisplay(mContext, badgeText, subject, snippet));
 
         // since spans affect text metrics, add spans to the string before measure/layout or fancy
         // ellipsizing
-        final int subjectTextLength = (subject != null) ? subject.length() : 0;
+
+        final int badgeTextLength = formatBadgeText(displayedStringBuilder, badgeText);
+
+        final int subjectTextLength = badgeTextLength + ((subject != null) ? subject.length() : 0)
+                + ((badgeTextLength > 0) ? 1 : 0);
         if (!TextUtils.isEmpty(subject)) {
             displayedStringBuilder.setSpan(TextAppearanceSpan.wrap(
-                    isUnread ? sSubjectTextUnreadSpan : sSubjectTextReadSpan), 0, subjectTextLength,
-                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    isUnread ? sSubjectTextUnreadSpan : sSubjectTextReadSpan),
+                    badgeTextLength, subjectTextLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
         }
         if (!TextUtils.isEmpty(snippet)) {
             final int startOffset = subjectTextLength;
@@ -1035,8 +1063,8 @@
                     displayedStringBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
         }
         if (isActivated() && showActivatedText()) {
-            displayedStringBuilder.setSpan(sActivatedTextSpan, 0, displayedStringBuilder.length(),
-                    Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+            displayedStringBuilder.setSpan(sActivatedTextSpan, badgeTextLength,
+                    displayedStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
         }
 
         final int subjectWidth = mCoordinates.subjectWidth;
@@ -1049,6 +1077,53 @@
         mSubjectTextView.setText(displayedStringBuilder);
     }
 
+    private void createBadge() {
+        // Do not create badge if in wide mode or badge text is empty.
+        final String badgeText = mHeader.badgeText;
+        if (!mCoordinates.isWideMode() || TextUtils.isEmpty(badgeText)) {
+            return;
+        }
+
+        final Spannable displayedBadgeString = new SpannableString(badgeText);
+        formatBadgeText(displayedBadgeString, badgeText);
+
+        final int badgeWidth = mCoordinates.badgeWidth;
+        final int badgeHeight = mCoordinates.badgeHeight;
+        mBadgeTextView.setLayoutParams(new ViewGroup.LayoutParams(badgeWidth, badgeHeight));
+        mBadgeTextView.setMaxLines(mCoordinates.badgeLineCount);
+        layoutViewExactly(mBadgeTextView, badgeWidth, badgeHeight);
+
+        mBadgeTextView.setText(displayedBadgeString);
+    }
+
+    private int formatBadgeText(Spannable displayedStringBuilder, String badgeText) {
+        final int badgeTextLength = (badgeText != null) ? badgeText.length() : 0;
+        if (!TextUtils.isEmpty(badgeText)) {
+            displayedStringBuilder.setSpan(TextAppearanceSpan.wrap(sBadgeTextSpan),
+                    0, badgeTextLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            displayedStringBuilder.setSpan(TextAppearanceSpan.wrap(sBadgeBackgroundSpan),
+                    0, badgeTextLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            displayedStringBuilder.setSpan(new BadgeSpan(displayedStringBuilder, this),
+                    0, badgeTextLength, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+
+        return badgeTextLength;
+    }
+
+    // START BadgeSpan.BadgeSpanDimensions override
+
+    @Override
+    public int getHorizontalPadding() {
+        return sBadgePaddingExtraWidth;
+    }
+
+    @Override
+    public float getRoundedCornerRadius() {
+        return sBadgeRoundedCornerRadius;
+    }
+
+    // END BadgeSpan.BadgeSpanDimensions override
+
     private boolean showActivatedText() {
         // For activated elements in tablet in conversation mode, we show an activated color, since
         // the background is dark blue for activated versus gray for non-activated.
@@ -1098,7 +1173,7 @@
         mPaperclipX = (isRtl) ? mDateX + mDateWidth + mCoordinates.datePaddingStart :
                 mDateX - ATTACHMENT.getWidth() - mCoordinates.datePaddingStart;
 
-        if (mCoordinates.isWide()) {
+        if (mCoordinates.isWideMode()) {
             // In wide mode, the end of the senders should align with
             // the start of the subject and is based on a max width.
             mSendersWidth = mCoordinates.sendersWidth;
@@ -1317,6 +1392,12 @@
         drawSubject(canvas);
         canvas.restore();
 
+        if (mCoordinates.isWideMode()) {
+            canvas.save();
+            drawBadge(canvas);
+            canvas.restore();
+        }
+
         // Folders.
         if (mConfig.areFoldersVisible()) {
             mHeader.folderDisplayer.drawFolders(canvas, mCoordinates, ViewUtils.isViewRtl(this));
@@ -1450,6 +1531,11 @@
         mSubjectTextView.draw(canvas);
     }
 
+    private void drawBadge(Canvas canvas) {
+        canvas.translate(mCoordinates.badgeLeft, mCoordinates.badgeTop);
+        mBadgeTextView.draw(canvas);
+    }
+
     private void drawSenders(Canvas canvas) {
         canvas.translate(mSendersX, mCoordinates.sendersY);
         mSendersTextView.draw(canvas);
@@ -1469,13 +1555,12 @@
      * 2. Tablet / Phone
      * 3. Checkbox checked / Unchecked (controls CAB color for item)
      * 4. Activated / Not activated (controls the blue highlight on tablet)
-     * @param isUnread
      */
-    private void updateBackground(boolean isUnread) {
+    private void updateBackground() {
         final int background;
         if (mBackgroundOverrideResId > 0) {
             background = mBackgroundOverrideResId;
-        } else if (isUnread) {
+        } else if (mHeader.unread) {
             background = R.drawable.conversation_unread_selector;
         } else {
             background = R.drawable.conversation_read_selector;
@@ -1605,7 +1690,7 @@
         }
 
         if (mStarEnabled) {
-            if (mCoordinates.getMode() == ConversationItemViewCoordinates.WIDE_MODE) {
+            if (mCoordinates.isWideMode()) {
                 // Just check that we're to start of the star's touch area
                 if (isTouchInStarTargetX(isRtl, x)) {
                     return false;
@@ -1628,7 +1713,7 @@
 
     private boolean isTouchInStar(float x, float y) {
         if (mHeader.infoIcon != null
-                && mCoordinates.getMode() != ConversationItemViewCoordinates.WIDE_MODE) {
+                && !mCoordinates.isWideMode()) {
             // We have an info icon, and it's above the star
             // We allow touches everywhere below the top of the subject text
             if (y < mCoordinates.subjectY) {
diff --git a/src/com/android/mail/browse/ConversationItemViewCoordinates.java b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
index 209cd16..a35539a 100644
--- a/src/com/android/mail/browse/ConversationItemViewCoordinates.java
+++ b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
@@ -17,6 +17,7 @@
 
 package com.android.mail.browse;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Paint.FontMetricsInt;
@@ -32,8 +33,6 @@
 import android.widget.TextView;
 
 import com.android.mail.R;
-import com.android.mail.R.dimen;
-import com.android.mail.R.id;
 import com.android.mail.ui.ViewMode;
 import com.android.mail.utils.Utils;
 import com.android.mail.utils.ViewUtils;
@@ -83,6 +82,7 @@
         private boolean mShowReplyState = false;
         private boolean mShowColorBlock = false;
         private boolean mShowPersonalIndicator = false;
+        private boolean mUseFullMargins = false;
 
         public Config setViewMode(int viewMode) {
             mViewMode = viewMode;
@@ -159,7 +159,8 @@
         private int getCacheKey() {
             // hash the attributes that contribute to item height and child view geometry
             return Objects.hashCode(mWidth, mViewMode, mGadgetMode, mAttachmentPreviewMode,
-                    mShowFolders, mShowReplyState, mShowPersonalIndicator);
+                    mShowFolders, mShowReplyState, mShowPersonalIndicator, mLayoutDirection,
+                    mUseFullMargins);
         }
 
         public Config setLayoutDirection(int layoutDirection) {
@@ -170,6 +171,15 @@
         public int getLayoutDirection() {
             return mLayoutDirection;
         }
+
+        public Config setUseFullMargins(boolean useFullMargins) {
+            mUseFullMargins = useFullMargins;
+            return this;
+        }
+
+        public boolean useFullPadding() {
+            return mUseFullMargins;
+        }
     }
 
     public static class CoordinatesCache {
@@ -212,9 +222,15 @@
     final int sendersWidth;
     final int sendersHeight;
     final int sendersLineCount;
-    final int sendersLineHeight;
     final float sendersFontSize;
 
+    // Badge. optional.
+    final int badgeLeft;
+    final int badgeTop;
+    final int badgeWidth;
+    final int badgeHeight;
+    final int badgeLineCount;
+
     // Subject.
     final int subjectX;
     final int subjectY;
@@ -316,7 +332,7 @@
         mMode = calculateMode(res, config);
 
         final int layoutId;
-        if (mMode == WIDE_MODE) {
+        if (isWideMode()) {
             layoutId = R.layout.conversation_item_view_wide;
         } else {
             if (config.getWidth() >= mMinListWidthIsSpacious) {
@@ -379,6 +395,8 @@
         personalIndicator.setVisibility(
                 config.isPersonalIndicatorVisible() ? View.VISIBLE : View.GONE);
 
+        setFramePadding(context, view, config.useFullPadding());
+
         // Layout the appropriate view.
         ViewCompat.setLayoutDirection(view, config.getLayoutDirection());
         final int widthSpec = MeasureSpec.makeMeasureSpec(config.getWidth(), MeasureSpec.EXACTLY);
@@ -413,13 +431,23 @@
         sendersWidth = senders.getWidth();
         sendersHeight = senders.getHeight();
         sendersLineCount = getLineCount(senders);
-        sendersLineHeight = senders.getLineHeight();
         sendersFontSize = senders.getTextSize();
 
+        final TextView badge = (TextView) view.findViewById(R.id.badge);
+        if (badge != null) {
+            badgeLeft = getX(badge);
+            badgeTop = getY(badge);
+            badgeWidth = senders.getWidth();
+            badgeHeight = senders.getHeight();
+            badgeLineCount = getLineCount(senders);
+        } else {
+            badgeLeft = badgeTop = badgeWidth = badgeHeight = badgeLineCount = 0;
+        }
+
         final TextView subject = (TextView) view.findViewById(R.id.subject);
         final int subjectTopAdjust = getLatinTopAdjustment(subject);
         subjectX = getX(subject);
-        if (isWide()) {
+        if (isWideMode()) {
             subjectY = getY(subject) + subjectTopAdjust;
         } else {
             subjectY = getY(subject) + sendersTopAdjust;
@@ -434,7 +462,7 @@
             final boolean isRtl = ViewUtils.isViewRtl(view);
             foldersLeft = (isRtl) ? 0 : subjectX;
             foldersRight = (isRtl) ? subjectX + subjectWidth : getX(folders) + folders.getWidth();
-            if (isWide()) {
+            if (isWideMode()) {
                 foldersY = getY(folders);
             } else {
                 foldersY = getY(folders) + sendersTopAdjust;
@@ -502,7 +530,7 @@
             attachmentPreviewsHeight = attachmentPreviews.getHeight();
 
             // We only care about the right and bottom of the overflow count
-            final TextView overflow = (TextView) view.findViewById(id.ap_overflow);
+            final TextView overflow = (TextView) view.findViewById(R.id.ap_overflow);
             final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) overflow
                     .getLayoutParams();
             overflowXEnd = attachmentPreviewsX + attachmentPreviewsWidth - params.rightMargin;
@@ -511,13 +539,13 @@
             overflowFontSize = overflow.getTextSize();
             overflowTypeface = overflow.getTypeface();
 
-            final View placeholder = view.findViewById(id.ap_placeholder);
+            final View placeholder = view.findViewById(R.id.ap_placeholder);
             placeholderWidth = placeholder.getWidth();
             placeholderHeight = placeholder.getHeight();
             placeholderY = attachmentPreviewsY + attachmentPreviewsHeight / 2
                     - placeholderHeight / 2;
 
-            final View progressBar = view.findViewById(id.ap_progress_bar);
+            final View progressBar = view.findViewById(R.id.ap_progress_bar);
             progressBarWidth = progressBar.getWidth();
             progressBarHeight = progressBar.getHeight();
             progressBarY = attachmentPreviewsY + attachmentPreviewsHeight / 2
@@ -540,15 +568,31 @@
             progressBarHeight = 0;
         }
 
-        height = view.getHeight() + (isWide() ? 0 : sendersTopAdjust);
+        height = view.getHeight() + (isWideMode() ? 0 : sendersTopAdjust);
         Utils.traceEndSection();
     }
 
+    @SuppressLint("NewApi")
+    private static void setFramePadding(Context context, ViewGroup view, boolean useFullPadding) {
+        final Resources res = context.getResources();
+        final int padding = res.getDimensionPixelSize(useFullPadding ?
+                R.dimen.conv_list_card_border_padding : R.dimen.conv_list_no_border_padding);
+
+        final View frame = view.findViewById(R.id.conversation_item_frame);
+        if (Utils.isRunningJBMR1OrLater()) {
+            // start, top, end, bottom
+            frame.setPaddingRelative(frame.getPaddingStart(), padding,
+                    frame.getPaddingEnd(), padding);
+        } else {
+            frame.setPadding(frame.getPaddingLeft(), padding, frame.getPaddingRight(), padding);
+        }
+    }
+
     public int getMode() {
         return mMode;
     }
 
-    public boolean isWide() {
+    public boolean isWideMode() {
         return mMode == WIDE_MODE;
     }
 
@@ -587,10 +631,11 @@
         final Resources res = context.getResources();
         switch (attachmentPreviewMode) {
             case ATTACHMENT_PREVIEW_UNREAD:
-                return (int) (isWide() ? res.getDimension(dimen.attachment_preview_height_tall_wide)
-                        : res.getDimension(dimen.attachment_preview_height_tall));
+                return (int) (isWideMode()
+                        ? res.getDimension(R.dimen.attachment_preview_height_tall_wide)
+                        : res.getDimension(R.dimen.attachment_preview_height_tall));
             case ATTACHMENT_PREVIEW_READ:
-                return (int) res.getDimension(dimen.attachment_preview_height_short);
+                return (int) res.getDimension(R.dimen.attachment_preview_height_short);
             default:
                 return 0;
         }
@@ -623,6 +668,7 @@
     /**
      * Returns the number of lines of this text view. Delegates to built-in TextView logic on JB+.
      */
+    @SuppressLint("NewApi")
     private static int getLineCount(TextView textView) {
         if (Utils.isRunningJellybeanOrLater()) {
             return textView.getMaxLines();
@@ -705,9 +751,4 @@
     public int getFolderMinimumWidth() {
         return mFolderMinimumWidth;
     }
-
-    public static boolean isWideMode(int mode) {
-        return mode == WIDE_MODE;
-    }
-
 }
diff --git a/src/com/android/mail/browse/ConversationItemViewModel.java b/src/com/android/mail/browse/ConversationItemViewModel.java
index 9ea145b..27216a5 100644
--- a/src/com/android/mail/browse/ConversationItemViewModel.java
+++ b/src/com/android/mail/browse/ConversationItemViewModel.java
@@ -67,13 +67,17 @@
 
     // Date
     CharSequence dateText;
-    public CharSequence dateOverrideText;
+    public boolean showDateText = true;
 
     // Personal level
     Bitmap personalLevelBitmap;
 
     public Bitmap infoIcon;
 
+    public String badgeText;
+
+    public int insetPadding = 0;
+
     // Paperclip
     Bitmap paperclip;
 
diff --git a/src/com/android/mail/browse/ConversationViewHeader.java b/src/com/android/mail/browse/ConversationViewHeader.java
index 2522990..c5fff83 100644
--- a/src/com/android/mail/browse/ConversationViewHeader.java
+++ b/src/com/android/mail/browse/ConversationViewHeader.java
@@ -17,6 +17,7 @@
 
 package com.android.mail.browse;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
 import android.text.Spannable;
@@ -113,6 +114,7 @@
         mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView, mIsRtl);
     }
 
+    @SuppressLint("NewApi")
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/src/com/android/mail/browse/FolderSpan.java b/src/com/android/mail/browse/FolderSpan.java
index 1bbd79b..12b9b05 100644
--- a/src/com/android/mail/browse/FolderSpan.java
+++ b/src/com/android/mail/browse/FolderSpan.java
@@ -136,8 +136,7 @@
                 left = x + paddingBefore;
                 right = x + bgWidth + paddingBefore;
             }
-            canvas.drawRect(left, top + paddingAbove, right, bottom,
-                    mWorkPaint);
+            canvas.drawRect(left, top + paddingAbove, right, bottom, mWorkPaint);
 
             mWorkPaint.setColor(prevColor);
             mWorkPaint.setStyle(prevStyle);
diff --git a/src/com/android/mail/providers/Conversation.java b/src/com/android/mail/providers/Conversation.java
index 24ed5b7..26d5506 100644
--- a/src/com/android/mail/providers/Conversation.java
+++ b/src/com/android/mail/providers/Conversation.java
@@ -176,6 +176,7 @@
     private transient boolean viewed;
 
     private static String sSubjectAndSnippet;
+    private static String sBadgeSubjectAndSnippet;
 
     // Constituents of convFlags below
     // Flag indicating that the item has been deleted, but will continue being
@@ -851,13 +852,9 @@
     /**
      * Get the properly formatted subject and snippet string for display a
      * conversation.
-     *
-     * @param context
-     * @param filteredSubject
-     * @param snippet
      */
     public static String getSubjectAndSnippetForDisplay(Context context,
-            String filteredSubject, String snippet) {
+            String badgeText, String filteredSubject, String snippet) {
         if (sSubjectAndSnippet == null) {
             sSubjectAndSnippet = context.getString(R.string.subject_and_snippet);
         }
@@ -867,6 +864,11 @@
             return snippet;
         } else if (TextUtils.isEmpty(snippet)) {
             return filteredSubject;
+        } else if (!TextUtils.isEmpty(badgeText)) {
+            if (sBadgeSubjectAndSnippet == null) {
+                sBadgeSubjectAndSnippet = context.getString(R.string.badge_subject_and_snippet);
+            }
+            return String.format(sBadgeSubjectAndSnippet, badgeText, filteredSubject, snippet);
         }
 
         return String.format(sSubjectAndSnippet, filteredSubject, snippet);
diff --git a/src/com/android/mail/widget/WidgetConversationListItemViewBuilder.java b/src/com/android/mail/widget/WidgetConversationListItemViewBuilder.java
index 585eddf..ea8ec90 100644
--- a/src/com/android/mail/widget/WidgetConversationListItemViewBuilder.java
+++ b/src/com/android/mail/widget/WidgetConversationListItemViewBuilder.java
@@ -16,12 +16,6 @@
 
 package com.android.mail.widget;
 
-import com.android.mail.R;
-import com.android.mail.providers.Conversation;
-import com.android.mail.providers.Folder;
-import com.android.mail.ui.FolderDisplayer;
-import com.android.mail.utils.FolderUri;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -35,6 +29,12 @@
 import android.view.View;
 import android.widget.RemoteViews;
 
+import com.android.mail.R;
+import com.android.mail.providers.Conversation;
+import com.android.mail.providers.Folder;
+import com.android.mail.ui.FolderDisplayer;
+import com.android.mail.utils.FolderUri;
+
 public class WidgetConversationListItemViewBuilder {
     // Static font sizes
     private static int DATE_FONT_SIZE;
@@ -160,7 +160,8 @@
         // Add style to subject
         final int subjectColor = isUnread ? SUBJECT_TEXT_COLOR_UNREAD : SUBJECT_TEXT_COLOR_READ;
         final SpannableStringBuilder subjectAndSnippet = new SpannableStringBuilder(
-                Conversation.getSubjectAndSnippetForDisplay(mContext, filteredSubject, snippet));
+                Conversation.getSubjectAndSnippetForDisplay(
+                        mContext, null /* badgeText */, filteredSubject, snippet));
         if (isUnread) {
             subjectAndSnippet.setSpan(new StyleSpan(Typeface.BOLD), 0, filteredSubject.length(),
                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);