177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao/**
277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Copyright (c) 2014, Google Inc.
377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao *
477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Licensed under the Apache License, Version 2.0 (the "License");
577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * you may not use this file except in compliance with the License.
677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * You may obtain a copy of the License at
777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao *
877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao *     http://www.apache.org/licenses/LICENSE-2.0
977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao *
1077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * Unless required by applicable law or agreed to in writing, software
1177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * distributed under the License is distributed on an "AS IS" BASIS,
1277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * See the License for the specific language governing permissions and
1477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao * limitations under the License.
1577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao */
1677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
1777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caopackage com.android.mail.utils;
1877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
1977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.graphics.Color;
2077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.graphics.Typeface;
2177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.SpannableStringBuilder;
2277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.Spanned;
23783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Caoimport android.text.style.AbsoluteSizeSpan;
2477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.ForegroundColorSpan;
2577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.QuoteSpan;
2677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.StyleSpan;
2777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.TypefaceSpan;
2877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.URLSpan;
2977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport android.text.style.UnderlineSpan;
3077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
3177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.android.mail.analytics.AnalyticsTimer;
32e26a92adf3f39d36995c67d31fe8eae3481f22d5Andy Huangimport com.google.android.mail.common.base.CharMatcher;
336c369ca7cd069c62ae839125088dffafb688631fJin Caoimport com.google.android.mail.common.html.parser.HTML;
346c369ca7cd069c62ae839125088dffafb688631fJin Caoimport com.google.android.mail.common.html.parser.HTML4;
3577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.google.android.mail.common.html.parser.HtmlDocument;
3677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.google.android.mail.common.html.parser.HtmlTree;
3777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport com.google.common.collect.Lists;
3877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
3977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caoimport java.util.LinkedList;
4077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
4177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Caopublic class HtmlUtils {
4277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
431d70245949b109f2e66779d0038b1f6bfd4f102dJin Cao    static final String LOG_TAG = LogTag.getLogTag();
441d70245949b109f2e66779d0038b1f6bfd4f102dJin Cao
4577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao    /**
4677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     * Use our custom SpannedConverter to process the HtmlNode results from HtmlTree.
4777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     * @param html
489ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang     * @return processed HTML as a Spanned
4977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     */
509ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang    public static Spanned htmlToSpan(String html, HtmlTree.ConverterFactory factory) {
5177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        AnalyticsTimer.getInstance().trackStart(AnalyticsTimer.COMPOSE_HTML_TO_SPAN);
5277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        // Get the html "tree"
5377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        final HtmlTree htmlTree = com.android.mail.utils.Utils.getHtmlTree(html);
549ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang        htmlTree.setConverterFactory(factory);
5577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        final Spanned spanned = htmlTree.getSpanned();
5677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        AnalyticsTimer.getInstance().logDuration(AnalyticsTimer.COMPOSE_HTML_TO_SPAN, true,
5777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                "compose", "html_to_span", null);
582871511a6076f92b4c7d017f0d7c1b8c4ec69a05Jin Cao        LogUtils.i(LOG_TAG, "htmlToSpan completed, input: %d, result: %d", html.length(),
591d70245949b109f2e66779d0038b1f6bfd4f102dJin Cao                spanned.length());
6077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        return spanned;
6177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao    }
6277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
6377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao    /**
6477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     * Class that handles converting the html into a Spanned.
6577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     * This class will only handle a subset of the html tags. Below is the full list:
6677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - bold
6777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - italic
6877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - underline
6977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - font size
7077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - font color
7177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - font face
7277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - a
7377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - blockquote
7477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - p
7577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     *   - div
7677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao     */
779ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang    public static class SpannedConverter implements HtmlTree.Converter<Spanned> {
78783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao        // Pinto normal text size is 2 while normal for AbsoluteSizeSpan is 12.
79783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao        // So 6 seems to be the magic number here. Html.toHtml also uses 6 as divider.
80783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao        private static final int WEB_TO_ANDROID_SIZE_MULTIPLIER = 6;
81783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao
829ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang        protected final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
8377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        private final LinkedList<TagWrapper> mSeenTags = Lists.newLinkedList();
8477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
857b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao        private final HtmlTree.DefaultPlainTextConverter mTextConverter =
867b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao                new HtmlTree.DefaultPlainTextConverter();
877b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao        private int mTextConverterIndex = 0;
88e26a92adf3f39d36995c67d31fe8eae3481f22d5Andy Huang
8977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        @Override
9077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        public void addNode(HtmlDocument.Node n, int nodeNum, int endNum) {
917b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            // Feed it into the plain text converter
927b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            mTextConverter.addNode(n, nodeNum, endNum);
937b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            if (n instanceof HtmlDocument.Tag) {
9477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                handleStart((HtmlDocument.Tag) n);
9577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            } else if (n instanceof HtmlDocument.EndTag) {
9677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                handleEnd((HtmlDocument.EndTag) n);
9777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
987b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            appendPlainTextFromConverter();
997b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao        }
1007b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao
1017b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao        private void appendPlainTextFromConverter() {
1027b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            String textString = mTextConverter.getObject();
1037b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            if (textString.length() > mTextConverterIndex) {
1047b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao                mBuilder.append(textString.substring(mTextConverterIndex));
1057b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao                mTextConverterIndex = textString.length();
1067b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            }
10777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        }
10877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
10977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        /**
11077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao         * Helper function to handle start tag
11177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao         */
1129ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang        protected void handleStart(HtmlDocument.Tag tag) {
11377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            if (!tag.isSelfTerminating()) {
11477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // Add to the stack of tags needing closing tag
1156c369ca7cd069c62ae839125088dffafb688631fJin Cao                mSeenTags.push(new TagWrapper(tag, mBuilder.length()));
11677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
11777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        }
11877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
11977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        /**
12077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao         * Helper function to handle end tag
12177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao         */
1229ed742cd9b19ab9390c97b3c35f7af443428ccbfAndy Huang        protected void handleEnd(HtmlDocument.EndTag tag) {
12377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            TagWrapper lastSeen;
1246c369ca7cd069c62ae839125088dffafb688631fJin Cao            HTML.Element element = tag.getElement();
1256c369ca7cd069c62ae839125088dffafb688631fJin Cao            while ((lastSeen = mSeenTags.poll()) != null && lastSeen.tag.getElement() != null &&
1266c369ca7cd069c62ae839125088dffafb688631fJin Cao                    !lastSeen.tag.getElement().equals(element)) { }
12777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
12877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            // Misformatted html, just ignore this tag
12977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            if (lastSeen == null) {
13077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                return;
13177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
13277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
1337b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            Object marker = null;
1346c369ca7cd069c62ae839125088dffafb688631fJin Cao            if (HTML4.B_ELEMENT.equals(element)) {
13577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // BOLD
13677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                marker = new StyleSpan(Typeface.BOLD);
1376c369ca7cd069c62ae839125088dffafb688631fJin Cao            } else if (HTML4.I_ELEMENT.equals(element)) {
13877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // ITALIC
13977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                marker = new StyleSpan(Typeface.ITALIC);
1406c369ca7cd069c62ae839125088dffafb688631fJin Cao            } else if (HTML4.U_ELEMENT.equals(element)) {
14177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // UNDERLINE
14277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                marker = new UnderlineSpan();
1436c369ca7cd069c62ae839125088dffafb688631fJin Cao            } else if (HTML4.A_ELEMENT.equals(element)) {
14477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // A HREF
1456c369ca7cd069c62ae839125088dffafb688631fJin Cao                HtmlDocument.TagAttribute attr = lastSeen.tag.getAttribute(HTML4.HREF_ATTRIBUTE);
14677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // Ignore this tag if it doesn't have a link
1476c369ca7cd069c62ae839125088dffafb688631fJin Cao                if (attr == null) {
14877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                    return;
14977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                }
1506c369ca7cd069c62ae839125088dffafb688631fJin Cao                marker = new URLSpan(attr.getValue());
1516c369ca7cd069c62ae839125088dffafb688631fJin Cao            } else if (HTML4.BLOCKQUOTE_ELEMENT.equals(element)) {
15277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // BLOCKQUOTE
15377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                marker = new QuoteSpan();
1546c369ca7cd069c62ae839125088dffafb688631fJin Cao            } else if (HTML4.FONT_ELEMENT.equals(element)) {
15577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // FONT SIZE/COLOR/FACE, since this can insert more than one span
15677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                // we special case it and return
15777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                handleFont(lastSeen);
15877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
15977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
16077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            final int start = lastSeen.startIndex;
16177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            final int end = mBuilder.length();
1627b966b191f98c818170609c2fe5af4478d6a23b2Jin Cao            if (marker != null && start != end) {
16377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                mBuilder.setSpan(marker, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
16477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
16577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        }
16677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
16777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        /**
16877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao         * Helper function to handle end font tags
16977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao         */
17077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        private void handleFont(TagWrapper wrapper) {
17177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            final int start = wrapper.startIndex;
17277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            final int end = mBuilder.length();
17377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
17477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            // check font color
1756c369ca7cd069c62ae839125088dffafb688631fJin Cao            HtmlDocument.TagAttribute attr = wrapper.tag.getAttribute(HTML4.COLOR_ATTRIBUTE);
1766c369ca7cd069c62ae839125088dffafb688631fJin Cao            if (attr != null) {
1776c369ca7cd069c62ae839125088dffafb688631fJin Cao                int c = Color.parseColor(attr.getValue());
17877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                if (c != -1) {
17977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                    mBuilder.setSpan(new ForegroundColorSpan(c | 0xFF000000), start, end,
18077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
18177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                }
18277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
18377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
18477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            // check font size
1856c369ca7cd069c62ae839125088dffafb688631fJin Cao            attr = wrapper.tag.getAttribute(HTML4.SIZE_ATTRIBUTE);
1866c369ca7cd069c62ae839125088dffafb688631fJin Cao            if (attr != null) {
1876c369ca7cd069c62ae839125088dffafb688631fJin Cao                int i = Integer.parseInt(attr.getValue());
18877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                if (i != -1) {
189783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao                    mBuilder.setSpan(new AbsoluteSizeSpan(i * WEB_TO_ANDROID_SIZE_MULTIPLIER,
190783e81cd330a20eb44e5ab81ba5d5c0df5152450Jin Cao                            true /* use dip */), start, end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
19177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                }
19277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
19377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
19477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            // check font typeface
1956c369ca7cd069c62ae839125088dffafb688631fJin Cao            attr = wrapper.tag.getAttribute(HTML4.FACE_ATTRIBUTE);
1966c369ca7cd069c62ae839125088dffafb688631fJin Cao            if (attr != null) {
1976c369ca7cd069c62ae839125088dffafb688631fJin Cao                String[] families = attr.getValue().split(",");
19877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                for (String family : families) {
19977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                    mBuilder.setSpan(new TypefaceSpan(family.trim()), start, end,
20077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
20177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                }
20277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
20377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        }
20477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
20577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        @Override
20677b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        public int getPlainTextLength() {
20777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            return mBuilder.length();
20877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        }
20977b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
21077b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        @Override
21177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        public Spanned getObject() {
21277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            return mBuilder;
21377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        }
21477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
21577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        private static class TagWrapper {
2166c369ca7cd069c62ae839125088dffafb688631fJin Cao            final HtmlDocument.Tag tag;
21777b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            final int startIndex;
21877b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao
2196c369ca7cd069c62ae839125088dffafb688631fJin Cao            TagWrapper(HtmlDocument.Tag tag, int startIndex) {
2206c369ca7cd069c62ae839125088dffafb688631fJin Cao                this.tag = tag;
22177b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao                this.startIndex = startIndex;
22277b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao            }
22377b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao        }
22477b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao    }
22577b4c2c31d7601665c337ce5cbc9d84fb9332be8Jin Cao}
226