Home | History | Annotate | Download | only in compose
      1 /**
      2  * Copyright (c) 2011, Google Inc.
      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 package com.android.mail.compose;
     17 
     18 import android.content.Context;
     19 import android.content.res.Resources;
     20 import android.text.Html;
     21 import android.text.SpannedString;
     22 import android.text.TextUtils;
     23 import android.util.AttributeSet;
     24 import android.view.LayoutInflater;
     25 import android.view.View;
     26 import android.view.View.OnClickListener;
     27 import android.webkit.WebSettings;
     28 import android.webkit.WebView;
     29 import android.widget.Button;
     30 import android.widget.CheckBox;
     31 import android.widget.LinearLayout;
     32 
     33 import com.android.mail.R;
     34 import com.android.mail.providers.Message;
     35 import com.android.mail.utils.Utils;
     36 
     37 import java.text.DateFormat;
     38 import java.util.Date;
     39 
     40 /*
     41  * View for displaying the quoted text in the compose screen for a reply
     42  * or forward. A close button is included in the upper right to remove
     43  * the quoted text from the message.
     44  */
     45 class QuotedTextView extends LinearLayout implements OnClickListener {
     46     // HTML tags used to quote reply content
     47     // The following style must be in-sync with
     48     // pinto.app.MessageUtil.QUOTE_STYLE and
     49     // java/com/google/caribou/ui/pinto/modules/app/messageutil.js
     50     // BEG_QUOTE_BIDI is also available there when we support BIDI
     51     private static final String BLOCKQUOTE_BEGIN = "<blockquote class=\"quote\" style=\""
     52             + "margin:0 0 0 .8ex;" + "border-left:1px #ccc solid;" + "padding-left:1ex\">";
     53     private static final String BLOCKQUOTE_END = "</blockquote>";
     54     private static final String QUOTE_END = "</div>";
     55 
     56     // Separates the attribution headers (Subject, To, etc) from the body in
     57     // quoted text.
     58     private static final String HEADER_SEPARATOR = "<br type='attribution'>";
     59     private static final int HEADER_SEPARATOR_LENGTH = HEADER_SEPARATOR.length();
     60 
     61     private CharSequence mQuotedText;
     62     private WebView mQuotedTextWebView;
     63     private ShowHideQuotedTextListener mShowHideListener;
     64     private CheckBox mQuotedTextCheckBox;
     65     private boolean mIncludeText = true;
     66     private Button mRespondInlineButton;
     67     private RespondInlineListener mRespondInlineListener;
     68     private static String sQuoteBegin;
     69 
     70     public QuotedTextView(Context context) {
     71         this(context, null);
     72     }
     73 
     74     public QuotedTextView(Context context, AttributeSet attrs) {
     75         this(context, attrs, -1);
     76     }
     77 
     78     public QuotedTextView(Context context, AttributeSet attrs, int defStyle) {
     79         super(context, attrs);
     80         LayoutInflater factory = LayoutInflater.from(context);
     81         factory.inflate(R.layout.quoted_text, this);
     82 
     83         mQuotedTextWebView = (WebView) findViewById(R.id.quoted_text_web_view);
     84         Utils.restrictWebView(mQuotedTextWebView);
     85         WebSettings settings = mQuotedTextWebView.getSettings();
     86         settings.setBlockNetworkLoads(true);
     87 
     88         mQuotedTextCheckBox = (CheckBox) findViewById(R.id.hide_quoted_text);
     89         mQuotedTextCheckBox.setChecked(true);
     90         mQuotedTextCheckBox.setOnClickListener(this);
     91         sQuoteBegin = context.getResources().getString(R.string.quote_begin);
     92 
     93 
     94         mRespondInlineButton = (Button) findViewById(R.id.respond_inline_button);
     95         if (mRespondInlineButton != null) {
     96             mRespondInlineButton.setEnabled(false);
     97         }
     98     }
     99 
    100     public void onDestroy() {
    101         if (mQuotedTextWebView != null) {
    102             mQuotedTextWebView.destroy();
    103         }
    104     }
    105 
    106     /**
    107      * Allow the user to include quoted text.
    108      * @param allow
    109      */
    110     public void allowQuotedText(boolean allow) {
    111         if (mQuotedTextCheckBox != null) {
    112             mQuotedTextCheckBox.setVisibility(allow ? View.VISIBLE : View.INVISIBLE);
    113         }
    114     }
    115 
    116     /**
    117      * Allow the user to respond inline.
    118      * @param allow
    119      */
    120     public void allowRespondInline(boolean allow) {
    121         if (mRespondInlineButton != null) {
    122             mRespondInlineButton.setVisibility(allow? View.VISIBLE : View.GONE);
    123         }
    124     }
    125 
    126     /**
    127      * Returns the quoted text if the user hasn't dismissed it, otherwise
    128      * returns null.
    129      */
    130     public CharSequence getQuotedTextIfIncluded() {
    131         if (mIncludeText) {
    132             return mQuotedText;
    133         }
    134         return null;
    135     }
    136 
    137     /**
    138      * Always returns the quoted text.
    139      */
    140     public CharSequence getQuotedText() {
    141         return mQuotedText;
    142     }
    143 
    144     /**
    145      * @return whether or not the user has selected to include quoted text.
    146      */
    147     public boolean isTextIncluded() {
    148         return mIncludeText;
    149     }
    150 
    151     public void setShowHideListener(ShowHideQuotedTextListener listener) {
    152         mShowHideListener = listener;
    153     }
    154 
    155 
    156     public void setRespondInlineListener(RespondInlineListener listener) {
    157         mRespondInlineListener = listener;
    158     }
    159 
    160     @Override
    161     public void onClick(View v) {
    162         final int id = v.getId();
    163 
    164         if (id == R.id.respond_inline_button) {
    165             respondInline();
    166         } else if (id == R.id.hide_quoted_text) {
    167             updateCheckedState(mQuotedTextCheckBox.isChecked());
    168         }
    169     }
    170 
    171     /**
    172      * Update the state of the checkbox for the QuotedTextView as if it were
    173      * tapped by the user. Also updates the visibility of the QuotedText area.
    174      * @param checked Either true or false.
    175      */
    176     public void updateCheckedState(boolean checked) {
    177         mQuotedTextCheckBox.setChecked(checked);
    178         updateQuotedTextVisibility(checked);
    179         if (mShowHideListener != null) {
    180             mShowHideListener.onShowHideQuotedText(checked);
    181         }
    182     }
    183 
    184     private void updateQuotedTextVisibility(boolean show) {
    185         mQuotedTextWebView.setVisibility(show ? View.VISIBLE : View.GONE);
    186         mIncludeText = show;
    187     }
    188 
    189     private void populateData() {
    190         String fontColor = getContext().getResources().getString(
    191                 R.string.quoted_text_font_color_string);
    192         String html = "<head><style type=\"text/css\">* body { color: " +
    193                 fontColor + "; }</style></head>" + mQuotedText.toString();
    194         mQuotedTextWebView.loadDataWithBaseURL(null, html, "text/html", "utf-8", null);
    195     }
    196 
    197     private void respondInline() {
    198         // Copy the text in the quoted message to the body of the
    199         // message after stripping the html.
    200         final String plainText = Utils.convertHtmlToPlainText(getQuotedText().toString());
    201         if (mRespondInlineListener != null) {
    202             mRespondInlineListener.onRespondInline("\n" + plainText);
    203         }
    204         // Set quoted text to unchecked and not visible.
    205         updateCheckedState(false);
    206         mRespondInlineButton.setVisibility(View.GONE);
    207         // Hide everything to do with quoted text.
    208         View quotedTextView = findViewById(R.id.quoted_text_area);
    209         if (quotedTextView != null) {
    210             quotedTextView.setVisibility(View.GONE);
    211         }
    212     }
    213 
    214     /**
    215      * Interface for listeners that want to be notified when quoted text
    216      * is shown / hidden.
    217      */
    218     public interface ShowHideQuotedTextListener {
    219         public void onShowHideQuotedText(boolean show);
    220     }
    221 
    222     /**
    223      * Interface for listeners that want to be notified when the user
    224      * chooses to respond inline.
    225      */
    226     public interface RespondInlineListener {
    227         public void onRespondInline(String text);
    228     }
    229 
    230     private static String getHtmlText(Message message) {
    231         if (message.bodyHtml != null) {
    232             return message.bodyHtml;
    233         } else if (message.bodyText != null) {
    234             // STOPSHIP Sanitize this
    235             return Html.toHtml(new SpannedString(message.bodyText));
    236         } else {
    237             return "";
    238         }
    239     }
    240 
    241     public void setQuotedText(int action, Message refMessage, boolean allow) {
    242         setVisibility(View.VISIBLE);
    243         String htmlText = getHtmlText(refMessage);
    244         StringBuilder quotedText = new StringBuilder();
    245         DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
    246         Date date = new Date(refMessage.dateReceivedMs);
    247         Resources resources = getContext().getResources();
    248         if (action == ComposeActivity.REPLY || action == ComposeActivity.REPLY_ALL) {
    249             quotedText.append(sQuoteBegin);
    250             quotedText
    251                     .append(String.format(
    252                             resources.getString(R.string.reply_attribution),
    253                             dateFormat.format(date),
    254                             Utils.cleanUpString(
    255                                     refMessage.getFrom(), true)));
    256             quotedText.append(HEADER_SEPARATOR);
    257             quotedText.append(BLOCKQUOTE_BEGIN);
    258             quotedText.append(htmlText);
    259             quotedText.append(BLOCKQUOTE_END);
    260             quotedText.append(QUOTE_END);
    261         } else if (action == ComposeActivity.FORWARD) {
    262             quotedText.append(sQuoteBegin);
    263             quotedText
    264                     .append(String.format(resources.getString(R.string.forward_attribution), Utils
    265                             .cleanUpString(refMessage.getFrom(),
    266                                     true /* remove empty quotes */), dateFormat.format(date), Utils
    267                             .cleanUpString(refMessage.subject,
    268                                     false /* don't remove empty quotes */), Utils.cleanUpString(
    269                             refMessage.getTo(), true)));
    270             String ccAddresses = refMessage.getCc();
    271             quotedText.append(String.format(resources.getString(R.string.cc_attribution),
    272                     Utils.cleanUpString(ccAddresses, true /* remove empty quotes */)));
    273             quotedText.append(HEADER_SEPARATOR);
    274             quotedText.append(BLOCKQUOTE_BEGIN);
    275             quotedText.append(htmlText);
    276             quotedText.append(BLOCKQUOTE_END);
    277             quotedText.append(QUOTE_END);
    278         }
    279         setQuotedText(quotedText);
    280         allowQuotedText(allow);
    281         // If there is quoted text, we always allow respond inline, since this
    282         // may be a forward.
    283         allowRespondInline(true);
    284     }
    285 
    286     public void setQuotedTextFromDraft(CharSequence htmlText, boolean forward) {
    287         setVisibility(View.VISIBLE);
    288         setQuotedText(htmlText);
    289         allowQuotedText(!forward);
    290         // If there is quoted text, we always allow respond inline, since this
    291         // may be a forward.
    292         allowRespondInline(true);
    293     }
    294 
    295     public void setQuotedTextFromHtml(CharSequence htmlText, boolean shouldQuoteText) {
    296         setVisibility(VISIBLE);
    297         if (shouldQuoteText) {
    298             final StringBuilder quotedText = new StringBuilder();
    299             final Resources resources = getContext().getResources();
    300             quotedText.append(sQuoteBegin);
    301             quotedText.append(
    302                     String.format(resources.getString(R.string.forward_attribution_no_headers)));
    303             quotedText.append(HEADER_SEPARATOR);
    304             quotedText.append(BLOCKQUOTE_BEGIN);
    305             quotedText.append(htmlText);
    306             quotedText.append(BLOCKQUOTE_END);
    307             quotedText.append(QUOTE_END);
    308             setQuotedText(quotedText);
    309         } else {
    310             setQuotedText(htmlText);
    311         }
    312         findViewById(R.id.divider_bar).setVisibility(GONE);
    313         findViewById(R.id.quoted_text_button_bar).setVisibility(GONE);
    314     }
    315     /**
    316      * Set quoted text. Some use cases may not want to display the check box (i.e. forwarding) so
    317      * allow control of that.
    318      */
    319     private void setQuotedText(CharSequence quotedText) {
    320         mQuotedText = quotedText;
    321         populateData();
    322         if (mRespondInlineButton != null) {
    323             if (!TextUtils.isEmpty(quotedText)) {
    324                 mRespondInlineButton.setVisibility(View.VISIBLE);
    325                 mRespondInlineButton.setEnabled(true);
    326                 mRespondInlineButton.setOnClickListener(this);
    327             } else {
    328                 // No text to copy; disable the respond inline button.
    329                 mRespondInlineButton.setVisibility(View.GONE);
    330                 mRespondInlineButton.setEnabled(false);
    331             }
    332         }
    333     }
    334 
    335     public static boolean containsQuotedText(String text) {
    336         int pos = text.indexOf(sQuoteBegin);
    337         return pos >= 0;
    338     }
    339 
    340     /**
    341      * Returns the index of the actual quoted text and NOT the meta information such as:
    342      * "On April 4, 2013 Joe Smith <jsmith (at) example.com> wrote:" that is part of the original
    343      * message when replying and including the original text.
    344      * @param text HTML text that includes quoted text.
    345      * @return The offset found.
    346      */
    347     public static int getQuotedTextOffset(String text) {
    348         return text.indexOf(QuotedTextView.HEADER_SEPARATOR)
    349                 + QuotedTextView.HEADER_SEPARATOR_LENGTH;
    350     }
    351 
    352     /**
    353      * Find the index of where the entire block of quoted text, quotes, divs,
    354      * attribution and all, begins.
    355      */
    356     public static int findQuotedTextIndex(CharSequence htmlText) {
    357         if (TextUtils.isEmpty(htmlText)) {
    358             return -1;
    359         }
    360         String textString = htmlText.toString();
    361         return textString.indexOf(sQuoteBegin);
    362     }
    363 
    364     public void setUpperDividerVisible(boolean visible) {
    365         findViewById(R.id.upper_quotedtext_divider_bar).setVisibility(
    366                 visible ? View.VISIBLE : View.GONE);
    367     }
    368 }
    369