• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.contacts.util;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.text.Html;
22 import android.text.Html.ImageGetter;
23 import android.text.Html.TagHandler;
24 import android.text.SpannableStringBuilder;
25 import android.text.Spanned;
26 import android.text.TextUtils;
27 import android.text.style.ImageSpan;
28 import android.text.style.QuoteSpan;
29 
30 import com.android.contacts.R;
31 import com.google.common.annotations.VisibleForTesting;
32 
33 /**
34  * Provides static functions to perform custom HTML to text conversions.
35  * Specifically, it adjusts the color and padding of the vertical
36  * stripe on block quotes and alignment of inlined images.
37  */
38 public class HtmlUtils {
39 
40     /**
41      * Converts HTML string to a {@link Spanned} text, adjusting formatting. Any extra new line
42      * characters at the end of the text will be trimmed.
43      */
fromHtml(Context context, String text)44     public static Spanned fromHtml(Context context, String text) {
45         if (TextUtils.isEmpty(text)) {
46             return null;
47         }
48         Spanned spanned = Html.fromHtml(text);
49         return postprocess(context, spanned);
50     }
51 
52     /**
53      * Converts HTML string to a {@link Spanned} text, adjusting formatting and using a custom
54      * image getter. Any extra new line characters at the end of the text will be trimmed.
55      */
fromHtml(Context context, String text, ImageGetter imageGetter, TagHandler tagHandler)56     public static CharSequence fromHtml(Context context, String text, ImageGetter imageGetter,
57             TagHandler tagHandler) {
58         if (TextUtils.isEmpty(text)) {
59             return null;
60         }
61         return postprocess(context, Html.fromHtml(text, imageGetter, tagHandler));
62     }
63 
64     /**
65      * Replaces some spans with custom versions of those. Any extra new line characters at the end
66      * of the text will be trimmed.
67      */
68     @VisibleForTesting
postprocess(Context context, Spanned original)69     static Spanned postprocess(Context context, Spanned original) {
70         if (original == null) {
71             return null;
72         }
73         final int length = original.length();
74         if (length == 0) {
75             return original; // Bail early.
76         }
77 
78         // If it's a SpannableStringBuilder, just use it.  Otherwise, create a new
79         // SpannableStringBuilder based on the passed Spanned.
80         final SpannableStringBuilder builder;
81         if (original instanceof SpannableStringBuilder) {
82             builder = (SpannableStringBuilder) original;
83         } else {
84             builder = new SpannableStringBuilder(original);
85         }
86 
87         final QuoteSpan[] quoteSpans = builder.getSpans(0, length, QuoteSpan.class);
88         if (quoteSpans != null && quoteSpans.length != 0) {
89             Resources resources = context.getResources();
90             int color = resources.getColor(R.color.stream_item_stripe_color);
91             int width = resources.getDimensionPixelSize(R.dimen.stream_item_stripe_width);
92             for (int i = 0; i < quoteSpans.length; i++) {
93                 replaceSpan(builder, quoteSpans[i], new StreamItemQuoteSpan(color, width));
94             }
95         }
96 
97         final ImageSpan[] imageSpans = builder.getSpans(0, length, ImageSpan.class);
98         if (imageSpans != null) {
99             for (int i = 0; i < imageSpans.length; i++) {
100                 ImageSpan span = imageSpans[i];
101                 replaceSpan(builder, span, new ImageSpan(span.getDrawable(),
102                         ImageSpan.ALIGN_BASELINE));
103             }
104         }
105 
106         // Trim the trailing new line characters at the end of the text (which can be added
107         // when HTML block quote tags are turned into new line characters).
108         int end = length;
109         for (int i = builder.length() - 1; i >= 0; i--) {
110             if (builder.charAt(i) != '\n') {
111                 break;
112             }
113             end = i;
114         }
115 
116         // If there's no trailing newlines, just return it.
117         if (end == length) {
118             return builder;
119         }
120 
121         // Otherwise, Return a substring of the original {@link Spanned} text
122         // from the start index (inclusive) to the end index (exclusive).
123         return new SpannableStringBuilder(builder, 0, end);
124     }
125 
126     /**
127      * Replaces one span with the other.
128      */
replaceSpan(SpannableStringBuilder builder, Object originalSpan, Object newSpan)129     private static void replaceSpan(SpannableStringBuilder builder, Object originalSpan,
130             Object newSpan) {
131         builder.setSpan(newSpan,
132                 builder.getSpanStart(originalSpan),
133                 builder.getSpanEnd(originalSpan),
134                 builder.getSpanFlags(originalSpan));
135         builder.removeSpan(originalSpan);
136     }
137 
138     public static class StreamItemQuoteSpan extends QuoteSpan {
139         private final int mWidth;
140 
StreamItemQuoteSpan(int color, int width)141         public StreamItemQuoteSpan(int color, int width) {
142             super(color);
143             this.mWidth = width;
144         }
145 
146         /**
147          * {@inheritDoc}
148          */
149         @Override
getLeadingMargin(boolean first)150         public int getLeadingMargin(boolean first) {
151             return mWidth;
152         }
153     }
154 }
155