| /** |
| * Copyright (c) 2011, Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.mail.compose; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.text.Html; |
| import android.text.SpannedString; |
| import android.text.TextUtils; |
| import android.util.AttributeSet; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.webkit.WebSettings; |
| import android.webkit.WebView; |
| import android.widget.Button; |
| import android.widget.CheckBox; |
| import android.widget.LinearLayout; |
| |
| import com.android.mail.R; |
| import com.android.mail.providers.Message; |
| import com.android.mail.utils.Utils; |
| |
| import java.text.DateFormat; |
| import java.util.Date; |
| |
| /* |
| * View for displaying the quoted text in the compose screen for a reply |
| * or forward. A close button is included in the upper right to remove |
| * the quoted text from the message. |
| */ |
| class QuotedTextView extends LinearLayout implements OnClickListener { |
| // HTML tags used to quote reply content |
| // The following style must be in-sync with |
| // pinto.app.MessageUtil.QUOTE_STYLE and |
| // java/com/google/caribou/ui/pinto/modules/app/messageutil.js |
| // BEG_QUOTE_BIDI is also available there when we support BIDI |
| private static final String BLOCKQUOTE_BEGIN = "<blockquote class=\"quote\" style=\"" |
| + "margin:0 0 0 .8ex;" + "border-left:1px #ccc solid;" + "padding-left:1ex\">"; |
| private static final String BLOCKQUOTE_END = "</blockquote>"; |
| private static final String QUOTE_END = "</div>"; |
| |
| // Separates the attribution headers (Subject, To, etc) from the body in |
| // quoted text. |
| private static final String HEADER_SEPARATOR = "<br type='attribution'>"; |
| private static final int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.length(); |
| |
| private CharSequence mQuotedText; |
| private WebView mQuotedTextWebView; |
| private ShowHideQuotedTextListener mShowHideListener; |
| private CheckBox mQuotedTextCheckBox; |
| private boolean mIncludeText = true; |
| private Button mRespondInlineButton; |
| private RespondInlineListener mRespondInlineListener; |
| private static String sQuoteBegin; |
| |
| public QuotedTextView(Context context) { |
| this(context, null); |
| } |
| |
| public QuotedTextView(Context context, AttributeSet attrs) { |
| this(context, attrs, -1); |
| } |
| |
| public QuotedTextView(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs); |
| LayoutInflater factory = LayoutInflater.from(context); |
| factory.inflate(R.layout.quoted_text, this); |
| |
| mQuotedTextWebView = (WebView) findViewById(R.id.quoted_text_web_view); |
| Utils.restrictWebView(mQuotedTextWebView); |
| WebSettings settings = mQuotedTextWebView.getSettings(); |
| settings.setBlockNetworkLoads(true); |
| |
| mQuotedTextCheckBox = (CheckBox) findViewById(R.id.hide_quoted_text); |
| mQuotedTextCheckBox.setChecked(true); |
| mQuotedTextCheckBox.setOnClickListener(this); |
| sQuoteBegin = context.getResources().getString(R.string.quote_begin); |
| |
| |
| mRespondInlineButton = (Button) findViewById(R.id.respond_inline_button); |
| if (mRespondInlineButton != null) { |
| mRespondInlineButton.setEnabled(false); |
| } |
| } |
| |
| public void onDestroy() { |
| if (mQuotedTextWebView != null) { |
| mQuotedTextWebView.destroy(); |
| } |
| } |
| |
| /** |
| * Allow the user to include quoted text. |
| * @param allow |
| */ |
| public void allowQuotedText(boolean allow) { |
| if (mQuotedTextCheckBox != null) { |
| mQuotedTextCheckBox.setVisibility(allow ? View.VISIBLE : View.INVISIBLE); |
| } |
| } |
| |
| /** |
| * Allow the user to respond inline. |
| * @param allow |
| */ |
| public void allowRespondInline(boolean allow) { |
| if (mRespondInlineButton != null) { |
| mRespondInlineButton.setVisibility(allow? View.VISIBLE : View.GONE); |
| } |
| } |
| |
| /** |
| * Returns the quoted text if the user hasn't dismissed it, otherwise |
| * returns null. |
| */ |
| public CharSequence getQuotedTextIfIncluded() { |
| if (mIncludeText) { |
| return mQuotedText; |
| } |
| return null; |
| } |
| |
| /** |
| * Always returns the quoted text. |
| */ |
| public CharSequence getQuotedText() { |
| return mQuotedText; |
| } |
| |
| /** |
| * @return whether or not the user has selected to include quoted text. |
| */ |
| public boolean isTextIncluded() { |
| return mIncludeText; |
| } |
| |
| public void setShowHideListener(ShowHideQuotedTextListener listener) { |
| mShowHideListener = listener; |
| } |
| |
| |
| public void setRespondInlineListener(RespondInlineListener listener) { |
| mRespondInlineListener = listener; |
| } |
| |
| @Override |
| public void onClick(View v) { |
| final int id = v.getId(); |
| |
| if (id == R.id.respond_inline_button) { |
| respondInline(); |
| } else if (id == R.id.hide_quoted_text) { |
| updateCheckedState(mQuotedTextCheckBox.isChecked()); |
| } |
| } |
| |
| /** |
| * Update the state of the checkbox for the QuotedTextView as if it were |
| * tapped by the user. Also updates the visibility of the QuotedText area. |
| * @param checked Either true or false. |
| */ |
| public void updateCheckedState(boolean checked) { |
| mQuotedTextCheckBox.setChecked(checked); |
| updateQuotedTextVisibility(checked); |
| if (mShowHideListener != null) { |
| mShowHideListener.onShowHideQuotedText(checked); |
| } |
| } |
| |
| private void updateQuotedTextVisibility(boolean show) { |
| mQuotedTextWebView.setVisibility(show ? View.VISIBLE : View.GONE); |
| mIncludeText = show; |
| } |
| |
| private void populateData() { |
| String fontColor = getContext().getResources().getString( |
| R.string.quoted_text_font_color_string); |
| String html = "<head><style type=\"text/css\">* body { color: " + |
| fontColor + "; }</style></head>" + mQuotedText.toString(); |
| mQuotedTextWebView.loadDataWithBaseURL(null, html, "text/html", "utf-8", null); |
| } |
| |
| private void respondInline() { |
| // Copy the text in the quoted message to the body of the |
| // message after stripping the html. |
| final String plainText = Utils.convertHtmlToPlainText(getQuotedText().toString()); |
| if (mRespondInlineListener != null) { |
| mRespondInlineListener.onRespondInline("\n" + plainText); |
| } |
| // Set quoted text to unchecked and not visible. |
| updateCheckedState(false); |
| mRespondInlineButton.setVisibility(View.GONE); |
| // Hide everything to do with quoted text. |
| View quotedTextView = findViewById(R.id.quoted_text_area); |
| if (quotedTextView != null) { |
| quotedTextView.setVisibility(View.GONE); |
| } |
| } |
| |
| /** |
| * Interface for listeners that want to be notified when quoted text |
| * is shown / hidden. |
| */ |
| public interface ShowHideQuotedTextListener { |
| public void onShowHideQuotedText(boolean show); |
| } |
| |
| /** |
| * Interface for listeners that want to be notified when the user |
| * chooses to respond inline. |
| */ |
| public interface RespondInlineListener { |
| public void onRespondInline(String text); |
| } |
| |
| private static String getHtmlText(Message message) { |
| if (message.bodyHtml != null) { |
| return message.bodyHtml; |
| } else if (message.bodyText != null) { |
| // STOPSHIP Sanitize this |
| return Html.toHtml(new SpannedString(message.bodyText)); |
| } else { |
| return ""; |
| } |
| } |
| |
| public void setQuotedText(int action, Message refMessage, boolean allow) { |
| setVisibility(View.VISIBLE); |
| String htmlText = getHtmlText(refMessage); |
| StringBuilder quotedText = new StringBuilder(); |
| DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT); |
| Date date = new Date(refMessage.dateReceivedMs); |
| Resources resources = getContext().getResources(); |
| if (action == ComposeActivity.REPLY || action == ComposeActivity.REPLY_ALL) { |
| quotedText.append(sQuoteBegin); |
| quotedText |
| .append(String.format( |
| resources.getString(R.string.reply_attribution), |
| dateFormat.format(date), |
| Utils.cleanUpString( |
| refMessage.getFrom(), true))); |
| quotedText.append(HEADER_SEPARATOR); |
| quotedText.append(BLOCKQUOTE_BEGIN); |
| quotedText.append(htmlText); |
| quotedText.append(BLOCKQUOTE_END); |
| quotedText.append(QUOTE_END); |
| } else if (action == ComposeActivity.FORWARD) { |
| quotedText.append(sQuoteBegin); |
| quotedText |
| .append(String.format(resources.getString(R.string.forward_attribution), Utils |
| .cleanUpString(refMessage.getFrom(), |
| true /* remove empty quotes */), dateFormat.format(date), Utils |
| .cleanUpString(refMessage.subject, |
| false /* don't remove empty quotes */), Utils.cleanUpString( |
| refMessage.getTo(), true))); |
| String ccAddresses = refMessage.getCc(); |
| quotedText.append(String.format(resources.getString(R.string.cc_attribution), |
| Utils.cleanUpString(ccAddresses, true /* remove empty quotes */))); |
| quotedText.append(HEADER_SEPARATOR); |
| quotedText.append(BLOCKQUOTE_BEGIN); |
| quotedText.append(htmlText); |
| quotedText.append(BLOCKQUOTE_END); |
| quotedText.append(QUOTE_END); |
| } |
| setQuotedText(quotedText); |
| allowQuotedText(allow); |
| // If there is quoted text, we always allow respond inline, since this |
| // may be a forward. |
| allowRespondInline(true); |
| } |
| |
| public void setQuotedTextFromDraft(CharSequence htmlText, boolean forward) { |
| setVisibility(View.VISIBLE); |
| setQuotedText(htmlText); |
| allowQuotedText(!forward); |
| // If there is quoted text, we always allow respond inline, since this |
| // may be a forward. |
| allowRespondInline(true); |
| } |
| |
| public void setQuotedTextFromHtml(CharSequence htmlText, boolean shouldQuoteText) { |
| setVisibility(VISIBLE); |
| if (shouldQuoteText) { |
| final StringBuilder quotedText = new StringBuilder(); |
| final Resources resources = getContext().getResources(); |
| quotedText.append(sQuoteBegin); |
| quotedText.append( |
| String.format(resources.getString(R.string.forward_attribution_no_headers))); |
| quotedText.append(HEADER_SEPARATOR); |
| quotedText.append(BLOCKQUOTE_BEGIN); |
| quotedText.append(htmlText); |
| quotedText.append(BLOCKQUOTE_END); |
| quotedText.append(QUOTE_END); |
| setQuotedText(quotedText); |
| } else { |
| setQuotedText(htmlText); |
| } |
| findViewById(R.id.divider_bar).setVisibility(GONE); |
| findViewById(R.id.quoted_text_button_bar).setVisibility(GONE); |
| } |
| /** |
| * Set quoted text. Some use cases may not want to display the check box (i.e. forwarding) so |
| * allow control of that. |
| */ |
| private void setQuotedText(CharSequence quotedText) { |
| mQuotedText = quotedText; |
| populateData(); |
| if (mRespondInlineButton != null) { |
| if (!TextUtils.isEmpty(quotedText)) { |
| mRespondInlineButton.setVisibility(View.VISIBLE); |
| mRespondInlineButton.setEnabled(true); |
| mRespondInlineButton.setOnClickListener(this); |
| } else { |
| // No text to copy; disable the respond inline button. |
| mRespondInlineButton.setVisibility(View.GONE); |
| mRespondInlineButton.setEnabled(false); |
| } |
| } |
| } |
| |
| public static boolean containsQuotedText(String text) { |
| int pos = text.indexOf(sQuoteBegin); |
| return pos >= 0; |
| } |
| |
| /** |
| * Returns the index of the actual quoted text and NOT the meta information such as: |
| * "On April 4, 2013 Joe Smith <jsmith@example.com> wrote:" that is part of the original |
| * message when replying and including the original text. |
| * @param text HTML text that includes quoted text. |
| * @return The offset found. |
| */ |
| public static int getQuotedTextOffset(String text) { |
| return text.indexOf(QuotedTextView.HEADER_SEPARATOR) |
| + QuotedTextView.HEADER_SEPARATOR_LENGTH; |
| } |
| |
| /** |
| * Find the index of where the entire block of quoted text, quotes, divs, |
| * attribution and all, begins. |
| */ |
| public static int findQuotedTextIndex(CharSequence htmlText) { |
| if (TextUtils.isEmpty(htmlText)) { |
| return -1; |
| } |
| String textString = htmlText.toString(); |
| return textString.indexOf(sQuoteBegin); |
| } |
| |
| public void setUpperDividerVisible(boolean visible) { |
| findViewById(R.id.upper_quotedtext_divider_bar).setVisibility( |
| visible ? View.VISIBLE : View.GONE); |
| } |
| } |