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 com.android.contacts.R; 20 import com.google.common.annotations.VisibleForTesting; 21 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.text.Html; 25 import android.text.Html.ImageGetter; 26 import android.text.Html.TagHandler; 27 import android.text.SpannableStringBuilder; 28 import android.text.Spanned; 29 import android.text.TextUtils; 30 import android.text.style.ImageSpan; 31 import android.text.style.QuoteSpan; 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 */ 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 */ 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 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 */ 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 141 public StreamItemQuoteSpan(int color, int width) { 142 super(color); 143 this.mWidth = width; 144 } 145 146 /** 147 * {@inheritDoc} 148 */ 149 @Override 150 public int getLeadingMargin(boolean first) { 151 return mWidth; 152 } 153 } 154 } 155