Posprocessing HTML for stream items.

- Aligning images
- Fixing the margin and color of the blockquote

Bug: 5250700
Bug: 5243091
Change-Id: Ife799c669ac98bf8f7033529fb2b0977ad91ce09
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5e8bd70..e83988b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -108,4 +108,7 @@
     <!--  Color of the semi-transparent shadow box on contact tiles -->
     <color name="contact_tile_shadow_box_color">#7F000000</color>
 
+    <!--  Color of the vertical stripe that goes on the left of a block quote inside a stream item -->
+    <color name="stream_item_stripe_color">#CCCCCC</color>
+
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 13bb791..b5eaa52 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -270,4 +270,7 @@
     <!--  Vertical and horizontal padding in between contact tiles -->
     <dimen name="contact_tile_divider_padding">1dip</dimen>
 
+    <!--  Width of the lead margin on the left of a block quote inside a stream item -->
+    <dimen name="stream_item_stripe_width">8dip</dimen>
+
 </resources>
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index cd4add6..f94629b 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -22,6 +22,7 @@
 import com.android.contacts.R;
 import com.android.contacts.preference.ContactsPreferences;
 import com.android.contacts.util.ContactBadgeUtil;
+import com.android.contacts.util.HtmlUtils;
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
 import com.google.common.annotations.VisibleForTesting;
@@ -236,7 +237,7 @@
         String photoUri = null;
         if (!contactData.getStreamItems().isEmpty()) {
             StreamItemEntry firstEntry = contactData.getStreamItems().get(0);
-            snippet = Html.fromHtml(firstEntry.getText());
+            snippet = HtmlUtils.fromHtml(context, firstEntry.getText());
             if (!firstEntry.getPhotos().isEmpty()) {
                 StreamItemPhotoEntry firstPhoto = firstEntry.getPhotos().get(0);
                 photoUri = firstPhoto.getPhotoUri();
@@ -338,10 +339,11 @@
                 R.id.stream_item_attribution);
         TextView commentsView = (TextView) rootView.findViewById(R.id.stream_item_comments);
         ImageGetter imageGetter = new DefaultImageGetter(context.getPackageManager());
-        htmlView.setText(Html.fromHtml(streamItem.getText(), imageGetter, null));
+        htmlView.setText(HtmlUtils.fromHtml(context, streamItem.getText(), imageGetter, null));
         attributionView.setText(ContactBadgeUtil.getSocialDate(streamItem, context));
         if (streamItem.getComments() != null) {
-            commentsView.setText(Html.fromHtml(streamItem.getComments(), imageGetter, null));
+            commentsView.setText(HtmlUtils.fromHtml(context, streamItem.getComments(), imageGetter,
+                    null));
             commentsView.setVisibility(View.VISIBLE);
         } else {
             commentsView.setVisibility(View.GONE);
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index dd44310..9d064a5 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -21,6 +21,7 @@
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.util.ContactBadgeUtil;
+import com.android.contacts.util.HtmlUtils;
 import com.android.contacts.util.StreamItemEntry;
 
 import android.app.PendingIntent;
@@ -36,7 +37,6 @@
 import android.net.Uri;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.StreamItems;
-import android.text.Html;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.style.AbsoluteSizeSpan;
@@ -208,7 +208,7 @@
         } else {
             // TODO: Rotate between all the stream items?
             StreamItemEntry streamItem = streamItems.get(0);
-            CharSequence status = Html.fromHtml(streamItem.getText());
+            CharSequence status = HtmlUtils.fromHtml(context, streamItem.getText());
             if (status.length() <= SHORT_SNIPPET_LENGTH) {
                 sb.append("\n");
             } else {
diff --git a/src/com/android/contacts/util/HtmlUtils.java b/src/com/android/contacts/util/HtmlUtils.java
new file mode 100644
index 0000000..4663657
--- /dev/null
+++ b/src/com/android/contacts/util/HtmlUtils.java
@@ -0,0 +1,101 @@
+package com.android.contacts.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.Html;
+import android.text.Html.ImageGetter;
+import android.text.Html.TagHandler;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ImageSpan;
+import android.text.style.QuoteSpan;
+
+import com.android.contacts.R;
+
+/**
+ * Provides static functions to perform custom HTML to text conversions.
+ * Specifically, it adjusts the color and padding of the vertical
+ * stripe on block quotes and alignment of inlined images.
+ */
+public class HtmlUtils {
+
+    /**
+     * Converts HTML string to a {@link Spanned} text, adjusting formatting.
+     */
+    public static Spanned fromHtml(Context context, String text) {
+        Spanned spanned = Html.fromHtml(text);
+        postprocess(context, spanned);
+        return spanned;
+    }
+
+    /**
+     * Converts HTML string to a {@link Spanned} text, adjusting formatting and using a custom
+     * image getter.
+     */
+    public static CharSequence fromHtml(Context context, String text, ImageGetter imageGetter,
+            TagHandler tagHandler) {
+        Spanned spanned = Html.fromHtml(text, imageGetter, tagHandler);
+        postprocess(context, spanned);
+        return spanned;
+    }
+
+    /**
+     * Replaces some spans with custom versions of those.
+     */
+    private static void postprocess(Context context, Spanned spanned) {
+        if (!(spanned instanceof SpannableStringBuilder)) {
+            return;
+        }
+
+        int length = spanned.length();
+
+        SpannableStringBuilder builder = (SpannableStringBuilder)spanned;
+        QuoteSpan[] quoteSpans = spanned.getSpans(0, length, QuoteSpan.class);
+        if (quoteSpans != null && quoteSpans.length != 0) {
+            Resources resources = context.getResources();
+            int color = resources.getColor(R.color.stream_item_stripe_color);
+            int width = resources.getDimensionPixelSize(R.dimen.stream_item_stripe_width);
+            for (int i = 0; i < quoteSpans.length; i++) {
+                replaceSpan(builder, quoteSpans[i], new StreamItemQuoteSpan(color, width));
+            }
+        }
+
+        ImageSpan[] imageSpans = spanned.getSpans(0, length, ImageSpan.class);
+        if (imageSpans != null) {
+            for (int i = 0; i < imageSpans.length; i++) {
+                ImageSpan span = imageSpans[i];
+                replaceSpan(builder, span, new ImageSpan(span.getDrawable(),
+                        ImageSpan.ALIGN_BASELINE));
+            }
+        }
+    }
+
+    /**
+     * Replaces one span with the other.
+     */
+    private static void replaceSpan(SpannableStringBuilder builder, Object originalSpan,
+            Object newSpan) {
+        builder.setSpan(newSpan,
+                builder.getSpanStart(originalSpan),
+                builder.getSpanEnd(originalSpan),
+                builder.getSpanFlags(originalSpan));
+        builder.removeSpan(originalSpan);
+    }
+
+    public static class StreamItemQuoteSpan extends QuoteSpan {
+        private final int mWidth;
+
+        public StreamItemQuoteSpan(int color, int width) {
+            super(color);
+            this.mWidth = width;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getLeadingMargin(boolean first) {
+            return mWidth;
+        }
+    }
+}