1f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/*
2f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Copyright (C) 2012 Google Inc.
3f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Licensed to The Android Open Source Project.
4f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
5f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Licensed under the Apache License, Version 2.0 (the "License");
6f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * you may not use this file except in compliance with the License.
7f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * You may obtain a copy of the License at
8f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
9f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *      http://www.apache.org/licenses/LICENSE-2.0
10f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
11f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Unless required by applicable law or agreed to in writing, software
12f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * distributed under the License is distributed on an "AS IS" BASIS,
13f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * See the License for the specific language governing permissions and
15f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * limitations under the License.
16f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */
17f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
18f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangpackage com.android.mail.ui;
19f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
20f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.content.Context;
21f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport android.content.res.Resources.NotFoundException;
22f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
23f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.R;
24b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag;
25f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport com.android.mail.utils.LogUtils;
264ca3eb4fea185a57b4ecd770f50fe9ae03af8ee0Andrew Sappersteinimport com.android.mail.utils.Utils;
279e2d407fdafeb874e640eb84017feaf784309075Scott Kennedyimport com.google.common.annotations.VisibleForTesting;
28f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
29f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport java.io.IOException;
30f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport java.io.InputStreamReader;
31f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport java.util.Formatter;
32f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangimport java.util.regex.Pattern;
33f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
34f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang/**
35f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Renders data into very simple string-substitution HTML templates for conversation view.
36f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
37f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Templates should be UTF-8 encoded HTML with '%s' placeholders to be substituted upon render.
38f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang * Plain-jane string substitution with '%s' is slightly faster than typed substitution.
39f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang *
40f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang */
41f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huangpublic class HtmlConversationTemplates {
42f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
43f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    /**
44f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * Prefix applied to a message id for use as a div id
45f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     */
46f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public static final String MESSAGE_PREFIX = "m";
47f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public static final int MESSAGE_PREFIX_LENGTH = MESSAGE_PREFIX.length();
48f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
49f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    // TODO: refine. too expensive to iterate over cursor and pre-calculate total. so either
50f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    // estimate it, or defer assembly until the end when size is known (deferring increases
51f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    // working set size vs. estimation but is exact).
52f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private static final int BUFFER_SIZE_CHARS = 64 * 1024;
53f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
54b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook    private static final String TAG = LogTag.getLogTag();
55f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
56f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    /**
57f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * Pattern for HTML img tags with a "src" attribute where the value is an absolutely-specified
58f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * HTTP or HTTPS URL. In other words, these are images with valid URLs that we should munge to
59f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * prevent WebView from firing bad onload handlers for them. Part of the workaround for
60f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * b/5522414.
61f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     *
62f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * Pattern documentation:
63f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * There are 3 top-level parts of the pattern:
64f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * 1. required preceding string
65f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * 2. the literal string "src"
66f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * 3. required trailing string
67f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     *
68f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * The preceding string must be an img tag "<img " with intermediate spaces allowed. The
69f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * trailing whitespace is required.
70f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * Non-whitespace chars are allowed before "src", but if they are present, they must be followed
71f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * by another whitespace char. The idea is to allow other attributes, and avoid matching on
72f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * "src" in a later attribute value as much as possible.
73f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     *
74f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * The following string must contain "=" and "http", with intermediate whitespace and single-
75f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * and double-quote allowed in between. The idea is to avoid matching Gmail-hosted relative URLs
76f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * for inline attachment images of the form "?view=KEYVALUES".
77f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     *
78f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     */
79f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private static final Pattern sAbsoluteImgUrlPattern = Pattern.compile(
80f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            "(<\\s*img\\s+(?:[^>]*\\s+)?)src(\\s*=[\\s'\"]*http)", Pattern.CASE_INSENSITIVE
81f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                    | Pattern.MULTILINE);
82f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    /**
83f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * The text replacement for {@link #sAbsoluteImgUrlPattern}. The "src" attribute is set to
84f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     * something inert and not left unset to minimize interactions with existing JS.
85f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang     */
863233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    private static final String IMG_URL_REPLACEMENT = "$1src='data:' blocked-src$2";
87f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
88f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private static boolean sLoadedTemplates;
89f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private static String sSuperCollapsed;
9014f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein    private static String sBorder;
91f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private static String sMessage;
92f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private static String sConversationUpper;
93f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private static String sConversationLower;
94f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
95f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private Context mContext;
96f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private Formatter mFormatter;
97f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private StringBuilder mBuilder;
98f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private boolean mInProgress = false;
99f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
100f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public HtmlConversationTemplates(Context context) {
101f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mContext = context;
102f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
103f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        // The templates are small (~2KB total in ICS MR2), so it's okay to load them once and keep
104f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        // them in memory.
105f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        if (!sLoadedTemplates) {
106f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            sLoadedTemplates = true;
107f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            sSuperCollapsed = readTemplate(R.raw.template_super_collapsed);
10814f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein            sBorder = readTemplate(R.raw.template_border);
109f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            sMessage = readTemplate(R.raw.template_message);
110f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            sConversationUpper = readTemplate(R.raw.template_conversation_upper);
111f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            sConversationLower = readTemplate(R.raw.template_conversation_lower);
112f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
113f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
114f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
115f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public void appendSuperCollapsedHtml(int firstCollapsed, int blockHeight) {
116f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        if (!mInProgress) {
117f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            throw new IllegalStateException("must call startConversation first");
118f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
119f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
120f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        append(sSuperCollapsed, firstCollapsed, blockHeight);
121f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
122f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
12314f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein    /**
12414f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein     * Adds a spacer for the border that vertically separates cards.
12514f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein     * @param blockHeight height of the border
12614f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein     */
12714f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein    public void appendBorder(int blockHeight) {
12814f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein        append(sBorder, blockHeight);
12914f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein    }
13014f937408fe2451a91b44d3cd7d141347e716775Andrew Sapperstein
131f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    @VisibleForTesting
132f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    static String replaceAbsoluteImgUrls(final String html) {
133f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        return sAbsoluteImgUrlPattern.matcher(html).replaceAll(IMG_URL_REPLACEMENT);
134f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
135f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
136606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    public void appendMessageHtml(HtmlMessage message, boolean isExpanded,
137256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang            boolean safeForImages, int headerHeight, int footerHeight) {
138f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
139f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        final String bodyDisplay = isExpanded ? "block" : "none";
1407bdc3750454efe59617b7df945eadd7e59bee954Andy Huang        final String expandedClass = isExpanded ? "expanded" : "";
1413233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang        final String showImagesClass = safeForImages ? "mail-show-images" : "";
142f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
143014ea4c15d147794789b9c5bf4e243fa08781ad9Andy Huang        String body = message.getBodyAsHtml();
144f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
145f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        /* Work around a WebView bug (5522414) in setBlockNetworkImage that causes img onload event
146f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * handlers to fire before an image is loaded.
147f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * WebView will report bad dimensions when revealing inline images with absolute URLs, but
148f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * we can prevent WebView from ever seeing those images by changing all img "src" attributes
149f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * into "gm-src" before loading the HTML. Parsing the potentially dirty HTML input is
150f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * prohibitively expensive with TagSoup, so use a little regular expression instead.
151f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         *
152f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * To limit the scope of this workaround, only use it on messages that the server claims to
153f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * have external resources, and even then, only use it on img tags where the src is absolute
154f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * (i.e. url does not begin with "?"). The existing JavaScript implementation of this
155f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * attribute swap will continue to handle inline image attachments (they have relative
156f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * URLs) and any false negatives that the regex misses. This maintains overall security
157f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         * level by not relying solely on the regex.
158f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang         */
159606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        if (!safeForImages && message.embedsExternalResources()) {
160f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            body = replaceAbsoluteImgUrls(body);
161f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
162f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
163f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        append(sMessage,
1643233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang                getMessageDomId(message),
1657bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                expandedClass,
166f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                headerHeight,
167f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                showImagesClass,
168f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                bodyDisplay,
1697bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                body,
170c7543579c6a97c0ae3341578332f56d4d226f34cAndy Huang                bodyDisplay,
1717bdc3750454efe59617b7df945eadd7e59bee954Andy Huang                footerHeight
172f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        );
173f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
174f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
175606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein    public String getMessageDomId(HtmlMessage msg) {
176606dbd7a44b8445e872c25c0fe080e3e12a47adfAndrew Sapperstein        return MESSAGE_PREFIX + msg.getId();
1773233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang    }
1783233bff8ae08a56543c9f5abf1bc6ab38f0574ceAndy Huang
179256b35c0a8287f48c28e0d1ba3fae65790063295Andy Huang    public void startConversation(int sideMargin, int conversationHeaderHeight) {
180f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        if (mInProgress) {
181f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            throw new IllegalStateException("must call startConversation first");
182f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
183f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
184f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        reset();
1854ca3eb4fea185a57b4ecd770f50fe9ae03af8ee0Andrew Sapperstein        final String border = Utils.isRunningKitkatOrLater() ?
1864ca3eb4fea185a57b4ecd770f50fe9ae03af8ee0Andrew Sapperstein                "img[blocked-src] { border: 1px solid #CCCCCC; }" : "";
1874ca3eb4fea185a57b4ecd770f50fe9ae03af8ee0Andrew Sapperstein        append(sConversationUpper, border,  sideMargin, conversationHeaderHeight);
188f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mInProgress = true;
189f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
190f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
19168141df2d855af6520a19ff9127f69463d49c3deAndrew Sapperstein    public String endConversation(String docBaseUri, String conversationBaseUri,
1925ea5a8330532a75c83cbb30993a14ee9b821fa2aAndy Huang            int viewportWidth, boolean enableContentReadySignal, boolean normalizeMessageWidths,
1930180f27c0998623b702274048b49cd4bec536cf1Andy Huang            boolean enableMungeTables, boolean enableMungeImages) {
194f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        if (!mInProgress) {
195f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            throw new IllegalStateException("must call startConversation first");
196f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
197f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
19869aecc996f92521e3b2e1a11f7d3797227868cf2Andy Huang        final String contentReadyClass = enableContentReadySignal ? "initial-load" : "";
19969aecc996f92521e3b2e1a11f7d3797227868cf2Andy Huang
20069aecc996f92521e3b2e1a11f7d3797227868cf2Andy Huang        append(sConversationLower, contentReadyClass, mContext.getString(R.string.hide_elided),
201cebcc64fbd69618ff89f9fac0bfe9b9e7d7ce104Paul Westbrook                mContext.getString(R.string.show_elided), docBaseUri, conversationBaseUri,
20268141df2d855af6520a19ff9127f69463d49c3deAndrew Sapperstein                viewportWidth, enableContentReadySignal, normalizeMessageWidths,
2030180f27c0998623b702274048b49cd4bec536cf1Andy Huang                enableMungeTables, enableMungeImages);
204f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
205f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mInProgress = false;
206f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
207f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        LogUtils.d(TAG, "rendered conversation of %d bytes, buffer capacity=%d",
208f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                mBuilder.length() << 1, mBuilder.capacity() << 1);
209f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
210f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        return emit();
211f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
212f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
21346dfba6160b55a582b344328067e3dafeb881dd9Andy Huang    public String emit() {
214f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        String out = mFormatter.toString();
215f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        // release the builder memory ASAP
216f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mFormatter = null;
217f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mBuilder = null;
218f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        return out;
219f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
220f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
221f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    public void reset() {
222f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mBuilder = new StringBuilder(BUFFER_SIZE_CHARS);
223f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mFormatter = new Formatter(mBuilder, null /* no localization */);
224f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
225f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
226f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private String readTemplate(int id) throws NotFoundException {
227f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        StringBuilder out = new StringBuilder();
228f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        InputStreamReader in = null;
229f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        try {
230f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            try {
231f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                in = new InputStreamReader(
232f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                        mContext.getResources().openRawResource(id), "UTF-8");
233f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                char[] buf = new char[4096];
234f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                int chars;
235f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
236f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                while ((chars=in.read(buf)) > 0) {
237f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                    out.append(buf, 0, chars);
238f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                }
239f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
240f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                return out.toString();
241f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
242f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            } finally {
243f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                if (in != null) {
244f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                    in.close();
245f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                }
246f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            }
247f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        } catch (IOException e) {
248f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang            throw new NotFoundException("Unable to open template id=" + Integer.toHexString(id)
249f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang                    + " exception=" + e.getMessage());
250f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        }
251f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
252f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
253f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    private void append(String template, Object... args) {
254f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang        mFormatter.format(template, args);
255f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang    }
256f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang
257f70fc4052b72a850bbb9be585d0f5a4877ee9448Andy Huang}
258