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 17package com.android.contacts.util; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.text.Html; 22import android.text.Html.ImageGetter; 23import android.text.Html.TagHandler; 24import android.text.SpannableStringBuilder; 25import android.text.Spanned; 26import android.text.TextUtils; 27import android.text.style.ImageSpan; 28import android.text.style.QuoteSpan; 29 30import com.android.contacts.R; 31import 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@VisibleForTesting 39public class HtmlUtils { 40 41 /** 42 * Converts HTML string to a {@link Spanned} text, adjusting formatting. Any extra new line 43 * characters at the end of the text will be trimmed. 44 */ 45 public static Spanned fromHtml(Context context, String text) { 46 if (TextUtils.isEmpty(text)) { 47 return null; 48 } 49 Spanned spanned = Html.fromHtml(text); 50 return postprocess(context, spanned); 51 } 52 53 /** 54 * Converts HTML string to a {@link Spanned} text, adjusting formatting and using a custom 55 * image getter. Any extra new line characters at the end of the text will be trimmed. 56 */ 57 public static CharSequence fromHtml(Context context, String text, ImageGetter imageGetter, 58 TagHandler tagHandler) { 59 if (TextUtils.isEmpty(text)) { 60 return null; 61 } 62 return postprocess(context, Html.fromHtml(text, imageGetter, tagHandler)); 63 } 64 65 /** 66 * Replaces some spans with custom versions of those. Any extra new line characters at the end 67 * of the text will be trimmed. 68 */ 69 @VisibleForTesting 70 static Spanned postprocess(Context context, Spanned original) { 71 if (original == null) { 72 return null; 73 } 74 final int length = original.length(); 75 if (length == 0) { 76 return original; // Bail early. 77 } 78 79 // If it's a SpannableStringBuilder, just use it. Otherwise, create a new 80 // SpannableStringBuilder based on the passed Spanned. 81 final SpannableStringBuilder builder; 82 if (original instanceof SpannableStringBuilder) { 83 builder = (SpannableStringBuilder) original; 84 } else { 85 builder = new SpannableStringBuilder(original); 86 } 87 88 final QuoteSpan[] quoteSpans = builder.getSpans(0, length, QuoteSpan.class); 89 if (quoteSpans != null && quoteSpans.length != 0) { 90 Resources resources = context.getResources(); 91 int color = resources.getColor(R.color.stream_item_stripe_color); 92 int width = resources.getDimensionPixelSize(R.dimen.stream_item_stripe_width); 93 for (int i = 0; i < quoteSpans.length; i++) { 94 replaceSpan(builder, quoteSpans[i], new StreamItemQuoteSpan(color, width)); 95 } 96 } 97 98 final ImageSpan[] imageSpans = builder.getSpans(0, length, ImageSpan.class); 99 if (imageSpans != null) { 100 for (int i = 0; i < imageSpans.length; i++) { 101 ImageSpan span = imageSpans[i]; 102 replaceSpan(builder, span, new ImageSpan(span.getDrawable(), 103 ImageSpan.ALIGN_BASELINE)); 104 } 105 } 106 107 // Trim the trailing new line characters at the end of the text (which can be added 108 // when HTML block quote tags are turned into new line characters). 109 int end = length; 110 for (int i = builder.length() - 1; i >= 0; i--) { 111 if (builder.charAt(i) != '\n') { 112 break; 113 } 114 end = i; 115 } 116 117 // If there's no trailing newlines, just return it. 118 if (end == length) { 119 return builder; 120 } 121 122 // Otherwise, Return a substring of the original {@link Spanned} text 123 // from the start index (inclusive) to the end index (exclusive). 124 return new SpannableStringBuilder(builder, 0, end); 125 } 126 127 /** 128 * Replaces one span with the other. 129 */ 130 private static void replaceSpan(SpannableStringBuilder builder, Object originalSpan, 131 Object newSpan) { 132 builder.setSpan(newSpan, 133 builder.getSpanStart(originalSpan), 134 builder.getSpanEnd(originalSpan), 135 builder.getSpanFlags(originalSpan)); 136 builder.removeSpan(originalSpan); 137 } 138 139 public static class StreamItemQuoteSpan extends QuoteSpan { 140 private final int mWidth; 141 142 public StreamItemQuoteSpan(int color, int width) { 143 super(color); 144 this.mWidth = width; 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 @Override 151 public int getLeadingMargin(boolean first) { 152 return mWidth; 153 } 154 } 155} 156