Home | History | Annotate | Download | only in activity
      1 /*
      2  * Copyright (C) 2008 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 
     17 package com.android.email.activity;
     18 
     19 import android.content.ContentUris;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.net.Uri;
     23 import android.test.ActivityInstrumentationTestCase2;
     24 import android.test.UiThreadTest;
     25 import android.test.suitebuilder.annotation.LargeTest;
     26 import android.test.suitebuilder.annotation.SmallTest;
     27 import android.util.Log;
     28 import android.view.View;
     29 import android.widget.EditText;
     30 import android.widget.MultiAutoCompleteTextView;
     31 
     32 import com.android.email.Email;
     33 import com.android.email.EmailAddressValidator;
     34 import com.android.email.R;
     35 import com.android.email.TestUtils;
     36 import com.android.emailcommon.Logging;
     37 import com.android.emailcommon.mail.Address;
     38 import com.android.emailcommon.mail.MessagingException;
     39 import com.android.emailcommon.provider.Account;
     40 import com.android.emailcommon.provider.EmailContent.Attachment;
     41 import com.android.emailcommon.provider.EmailContent.Message;
     42 import com.google.android.collect.Lists;
     43 
     44 import java.util.ArrayList;
     45 
     46 
     47 /**
     48  * Various instrumentation tests for MessageCompose.
     49  *
     50  * It might be possible to convert these to ActivityUnitTest, which would be faster.
     51  *
     52  * You can run this entire test case with:
     53  *   runtest -c com.android.email.activity.MessageComposeTests email
     54  */
     55 @LargeTest
     56 public class MessageComposeTests
     57         extends ActivityInstrumentationTestCase2<MessageCompose> {
     58 
     59     private Context mContext;
     60 
     61     private MultiAutoCompleteTextView mToView;
     62     private MultiAutoCompleteTextView mCcView;
     63     private MultiAutoCompleteTextView mBccView;
     64     private EditText mSubjectView;
     65     private EditText mMessageView;
     66     private long mCreatedAccountId = -1;
     67     private String mSignature;
     68 
     69     private static final String ACCOUNT = "account (at) android.com";
     70     private static final String SENDER = "sender (at) android.com";
     71     private static final String REPLYTO = "replyto (at) android.com";
     72     private static final String RECIPIENT_TO = "recipient-to (at) android.com";
     73     private static final String RECIPIENT_CC = "recipient-cc (at) android.com";
     74     private static final String RECIPIENT_BCC = "recipient-bcc (at) android.com";
     75     private static final String SUBJECT = "This is the subject";
     76     private static final String BODY = "This is the body.  This is also the body.";
     77     private static final String SIGNATURE = "signature";
     78 
     79     private static final String FROM = "Fred From <from (at) google.com>";
     80     private static final String TO1 = "First To <first.to (at) google.com>";
     81     private static final String TO2 = "Second To <second.to (at) google.com>";
     82     private static final String TO3 = "CopyFirst Cc <first.cc (at) google.com>";
     83     private static final String CC1 = "First Cc <first.cc (at) google.com>";
     84     private static final String CC2 = "Second Cc <second.cc (at) google.com>";
     85     private static final String CC3 = "Third Cc <third.cc (at) google.com>";
     86     private static final String CC4 = "CopySecond To <second.to (at) google.com>";
     87 
     88     private static final String UTF16_SENDER =
     89             "\u3042\u3044\u3046 \u3048\u304A <sender (at) android.com>";
     90     private static final String UTF16_REPLYTO =
     91             "\u3042\u3044\u3046\u3048\u304A <replyto (at) android.com>";
     92     private static final String UTF16_RECIPIENT_TO =
     93             "\"\u3042\u3044\u3046,\u3048\u304A\" <recipient-to (at) android.com>";
     94     private static final String UTF16_RECIPIENT_CC =
     95             "\u30A2\u30AB \u30B5\u30BF\u30CA <recipient-cc (at) android.com>";
     96     private static final String UTF16_RECIPIENT_BCC =
     97             "\"\u30A2\u30AB,\u30B5\u30BF\u30CA\" <recipient-bcc (at) android.com>";
     98     private static final String UTF16_SUBJECT = "\u304A\u5BFF\u53F8\u306B\u3059\u308B\uFF1F";
     99     private static final String UTF16_BODY = "\u65E5\u672C\u8A9E\u306E\u6587\u7AE0";
    100 
    101     private static final String UTF32_SENDER =
    102             "\uD834\uDF01\uD834\uDF46 \uD834\uDF22 <sender (at) android.com>";
    103     private static final String UTF32_REPLYTO =
    104             "\uD834\uDF01\uD834\uDF46\uD834\uDF22 <replyto (at) android.com>";
    105     private static final String UTF32_RECIPIENT_TO =
    106             "\"\uD834\uDF01\uD834\uDF46,\uD834\uDF22\" <recipient-to (at) android.com>";
    107     private static final String UTF32_RECIPIENT_CC =
    108             "\uD834\uDF22 \uD834\uDF01\uD834\uDF46 <recipient-cc (at) android.com>";
    109     private static final String UTF32_RECIPIENT_BCC =
    110             "\"\uD834\uDF22,\uD834\uDF01\uD834\uDF46\" <recipient-bcc (at) android.com>";
    111     private static final String UTF32_SUBJECT = "\uD834\uDF01\uD834\uDF46";
    112     private static final String UTF32_BODY = "\uD834\uDF01\uD834\uDF46";
    113 
    114     /*
    115      * The following action definitions are purposefully copied from MessageCompose, so that
    116      * any changes to the action strings will break these tests. Changes to the actions should
    117      * be done consciously to think about existing shortcuts and clients.
    118      */
    119 
    120     private static final String ACTION_REPLY = "com.android.email.intent.action.REPLY";
    121     private static final String ACTION_REPLY_ALL = "com.android.email.intent.action.REPLY_ALL";
    122     private static final String ACTION_FORWARD = "com.android.email.intent.action.FORWARD";
    123     private static final String ACTION_EDIT_DRAFT = "com.android.email.intent.action.EDIT_DRAFT";
    124 
    125     public MessageComposeTests() {
    126         super(MessageCompose.class);
    127     }
    128 
    129     /*
    130      * The Message Composer activity is only enabled if one or more accounts
    131      * are configured on the device and a default account has been specified,
    132      * so we do that here before every test.
    133      */
    134     @Override
    135     protected void setUp() throws Exception {
    136         super.setUp();
    137         mContext = getInstrumentation().getTargetContext();
    138 
    139         // Force assignment of a default account
    140         long accountId = Account.getDefaultAccountId(mContext);
    141         if (accountId == -1) {
    142             Account account = new Account();
    143             account.mSenderName = "Bob Sender";
    144             account.mEmailAddress = "bob (at) sender.com";
    145             account.mSignature = SIGNATURE;
    146             account.save(mContext);
    147             accountId = account.mId;
    148             mCreatedAccountId = accountId;
    149         }
    150         Account account = Account.restoreAccountWithId(mContext, accountId);
    151         mSignature = account.getSignature();
    152         Email.setServicesEnabledSync(mContext);
    153 
    154         Intent intent = new Intent(Intent.ACTION_VIEW);
    155         setActivityIntent(intent);
    156         final MessageCompose a = getActivity();
    157         mToView = (MultiAutoCompleteTextView) a.findViewById(R.id.to);
    158         mCcView = (MultiAutoCompleteTextView) a.findViewById(R.id.cc);
    159         mBccView = (MultiAutoCompleteTextView) a.findViewById(R.id.bcc);
    160         mSubjectView = (EditText) a.findViewById(R.id.subject);
    161         mMessageView = (EditText) a.findViewById(R.id.message_content);
    162     }
    163 
    164     @Override
    165     protected void tearDown() throws Exception {
    166         super.tearDown();
    167         Context context = getInstrumentation().getTargetContext();
    168         // If we created an account, delete it here
    169         if (mCreatedAccountId > -1) {
    170             context.getContentResolver().delete(
    171                     ContentUris.withAppendedId(Account.CONTENT_URI, mCreatedAccountId), null, null);
    172         }
    173     }
    174 
    175     /**
    176      * The name 'test preconditions' is a convention to signal that if this
    177      * test doesn't pass, the test case was not set up properly and it might
    178      * explain any and all failures in other tests.  This is not guaranteed
    179      * to run before other tests, as junit uses reflection to find the tests.
    180      */
    181     public void testPreconditions() {
    182         assertNotNull(mToView);
    183         assertEquals(0, mToView.length());
    184         assertNotNull(mSubjectView);
    185         assertEquals(0, mSubjectView.length());
    186         assertNotNull(mMessageView);
    187 
    188         // Note that the signature is always preceeded with a newline.
    189         int sigLength = (mSignature == null) ? 0 : (1 + mSignature.length());
    190         assertEquals(sigLength, mMessageView.length());
    191     }
    192 
    193      /**
    194      * Test a couple of variations of processSourceMessage() for REPLY.
    195      *   To = Reply-To or From:  (if REPLY)
    196      *   To = (Reply-To or From:), Cc = + To: + Cc:   (if REPLY_ALL)
    197      *   Subject = Re: Subject
    198      *   Body = empty  (and has cursor)
    199      *
    200      *   TODO test REPLY_ALL
    201      */
    202     public void testProcessSourceMessageReply() throws MessagingException, Throwable {
    203         final Message message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
    204         Intent intent = new Intent(ACTION_REPLY);
    205         final MessageCompose a = getActivity();
    206         a.setIntent(intent);
    207         final Account account = new Account();
    208         account.mEmailAddress = ACCOUNT;
    209 
    210         runTestOnUiThread(new Runnable() {
    211             public void run() {
    212                 a.processSourceMessage(message, account);
    213                 a.setInitialComposeText(null, null);
    214                 checkFields(SENDER + ", ", null, null, "Re: " + SUBJECT, null, null);
    215                 checkFocused(mMessageView);
    216             }
    217         });
    218 
    219         message.mFrom = null;
    220         message.mReplyTo = Address.parseAndPack(REPLYTO);
    221 
    222         runTestOnUiThread(new Runnable() {
    223             public void run() {
    224                 resetViews();
    225                 a.processSourceMessage(message, account);
    226                 a.setInitialComposeText(null, null);
    227                 checkFields(REPLYTO + ", ", null, null, "Re: " + SUBJECT, null, null);
    228                 checkFocused(mMessageView);
    229             }
    230         });
    231     }
    232 
    233     /**
    234      * Tests similar cases as testProcessSourceMessageReply, but when sender is in From: (or
    235      * Reply-to: if applicable) field.
    236      *
    237      * To = To (if REPLY)
    238      * To = To, Cc = Cc (if REPLY_ALL)
    239      * Subject = Re: Subject
    240      * Body = empty  (and has cursor)
    241      *
    242      * @throws MessagingException
    243      * @throws Throwable
    244      */
    245     public void testRepliesWithREplyToFields() throws MessagingException, Throwable {
    246         final Message message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
    247         message.mCc = RECIPIENT_CC;
    248         Intent intent = new Intent(ACTION_REPLY);
    249         final MessageCompose a = getActivity();
    250         a.setIntent(intent);
    251         final Account account = new Account();
    252         account.mEmailAddress = SENDER;
    253 
    254         runTestOnUiThread(new Runnable() {
    255             public void run() {
    256                 a.processSourceMessage(message, account);
    257                 a.setInitialComposeText(null, null);
    258                 checkFields(RECIPIENT_TO + ", ", null, null, "Re: " + SUBJECT, null, null);
    259                 checkFocused(mMessageView);
    260             }
    261         });
    262 
    263         message.mFrom = null;
    264         message.mReplyTo = Address.parseAndPack(SENDER);
    265 
    266         runTestOnUiThread(new Runnable() {
    267             public void run() {
    268                 resetViews();
    269                 a.processSourceMessage(message, account);
    270                 a.setInitialComposeText(null, null);
    271                 checkFields(RECIPIENT_TO + ", ", null, null, "Re: " + SUBJECT, null, null);
    272                 checkFocused(mMessageView);
    273             }
    274         });
    275 
    276         a.setIntent(new Intent(ACTION_REPLY_ALL));
    277         runTestOnUiThread(new Runnable() {
    278             public void run() {
    279                 resetViews();
    280                 a.processSourceMessage(message, account);
    281                 a.setInitialComposeText(null, null);
    282                 checkFields(RECIPIENT_TO + ", ", RECIPIENT_CC + ", ", null,
    283                         "Re: " + SUBJECT, null, null);
    284                 checkFocused(mMessageView);
    285             }
    286         });
    287     }
    288 
    289     public void testProcessSourceMessageReplyWithSignature() throws MessagingException, Throwable {
    290         final Message message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
    291         Intent intent = new Intent(ACTION_REPLY);
    292         final MessageCompose a = getActivity();
    293         a.setIntent(intent);
    294         final Account account = new Account();
    295         account.mEmailAddress = ACCOUNT;
    296         account.mSignature = SIGNATURE;
    297         runTestOnUiThread(new Runnable() {
    298             public void run() {
    299                 a.processSourceMessage(message, account);
    300                 a.setInitialComposeText(null, SIGNATURE);
    301                 checkFields(SENDER + ", ", null, null, "Re: " + SUBJECT, null, SIGNATURE);
    302                 checkFocused(mMessageView);
    303             }
    304         });
    305 
    306         message.mFrom = null;
    307         message.mReplyTo = Address.parseAndPack(REPLYTO);
    308 
    309         runTestOnUiThread(new Runnable() {
    310             public void run() {
    311                 resetViews();
    312                 a.processSourceMessage(message, account);
    313                 a.setInitialComposeText(null, SIGNATURE);
    314                 checkFields(REPLYTO + ", ", null, null, "Re: " + SUBJECT, null, SIGNATURE);
    315                 checkFocused(mMessageView);
    316             }
    317         });
    318     }
    319 
    320     public void testProcessSourceMessageForwardWithSignature()
    321             throws MessagingException, Throwable {
    322         final Message message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
    323         Intent intent = new Intent(ACTION_FORWARD);
    324         final MessageCompose a = getActivity();
    325         a.setIntent(intent);
    326         final Account account = new Account();
    327         account.mSignature = SIGNATURE;
    328         runTestOnUiThread(new Runnable() {
    329             public void run() {
    330                 a.processSourceMessage(message, account);
    331                 a.setInitialComposeText(null, SIGNATURE);
    332                 checkFields(null, null, null, "Fwd: " + SUBJECT, null, SIGNATURE);
    333                 checkFocused(mToView);
    334            }
    335         });
    336     }
    337 
    338     /**
    339      * Test reply to utf-16 name and address
    340      */
    341     public void testProcessSourceMessageReplyUtf16() throws MessagingException, Throwable {
    342         final Message message = buildTestMessage(UTF16_RECIPIENT_TO, UTF16_SENDER,
    343                 UTF16_SUBJECT, UTF16_BODY);
    344         Intent intent = new Intent(ACTION_REPLY);
    345         final MessageCompose a = getActivity();
    346         a.setIntent(intent);
    347         final Account account = new Account();
    348         account.mEmailAddress = ACCOUNT;
    349 
    350         runTestOnUiThread(new Runnable() {
    351             public void run() {
    352                 a.processSourceMessage(message, account);
    353                 a.setInitialComposeText(null, null);
    354                 checkFields(UTF16_SENDER + ", ", null, null, "Re: " + UTF16_SUBJECT, null, null);
    355                 checkFocused(mMessageView);
    356             }
    357         });
    358 
    359         message.mFrom = null;
    360         message.mReplyTo = Address.parseAndPack(UTF16_REPLYTO);
    361 
    362         runTestOnUiThread(new Runnable() {
    363             public void run() {
    364                 resetViews();
    365                 a.processSourceMessage(message, account);
    366                 a.setInitialComposeText(null, null);
    367                 checkFields(UTF16_REPLYTO + ", ", null, null, "Re: " + UTF16_SUBJECT, null, null);
    368                 checkFocused(mMessageView);
    369             }
    370         });
    371     }
    372 
    373     /**
    374      * Test reply to utf-32 name and address
    375      */
    376     public void testProcessSourceMessageReplyUtf32() throws MessagingException, Throwable {
    377         final Message message = buildTestMessage(UTF32_RECIPIENT_TO, UTF32_SENDER,
    378                 UTF32_SUBJECT, UTF32_BODY);
    379         Intent intent = new Intent(ACTION_REPLY);
    380         final MessageCompose a = getActivity();
    381         a.setIntent(intent);
    382         final Account account = new Account();
    383         account.mEmailAddress = ACCOUNT;
    384 
    385         runTestOnUiThread(new Runnable() {
    386             public void run() {
    387                 a.processSourceMessage(message, account);
    388                 a.setInitialComposeText(null, null);
    389                 checkFields(UTF32_SENDER + ", ", null, null, "Re: " + UTF32_SUBJECT, null, null);
    390                 checkFocused(mMessageView);
    391             }
    392         });
    393 
    394         message.mFrom = null;
    395         message.mReplyTo = Address.parseAndPack(UTF32_REPLYTO);
    396 
    397         runTestOnUiThread(new Runnable() {
    398             public void run() {
    399                 resetViews();
    400                 a.processSourceMessage(message, account);
    401                 a.setInitialComposeText(null, null);
    402                 checkFields(UTF32_REPLYTO + ", ", null, null, "Re: " + UTF32_SUBJECT, null, null);
    403                 checkFocused(mMessageView);
    404             }
    405         });
    406     }
    407 
    408     /**
    409      * Test processSourceMessage() for FORWARD
    410      *   To = empty  (and has cursor)
    411      *   Subject = Fwd: Subject
    412      *   Body = empty
    413      */
    414     public void testProcessSourceMessageForward() throws MessagingException, Throwable {
    415         final Message message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
    416         Intent intent = new Intent(ACTION_FORWARD);
    417         final MessageCompose a = getActivity();
    418         a.setIntent(intent);
    419 
    420         runTestOnUiThread(new Runnable() {
    421             public void run() {
    422                 a.processSourceMessage(message, null);
    423                 a.setInitialComposeText(null, null);
    424                 checkFields(null, null, null, "Fwd: " + SUBJECT, null, null);
    425                 checkFocused(mToView);
    426             }
    427         });
    428     }
    429 
    430     /**
    431      * Test processSourceMessage() for EDIT_DRAFT
    432      * Reply and ReplyAll should map:
    433      *   To = to
    434      *   Subject = Subject
    435      *   Body = body (has cursor)
    436      *
    437      * TODO check CC and BCC handling too
    438      */
    439     public void testProcessDraftMessage() throws MessagingException, Throwable {
    440 
    441         final Message message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY);
    442         Intent intent = new Intent(ACTION_EDIT_DRAFT);
    443         final MessageCompose a = getActivity();
    444         a.setIntent(intent);
    445 
    446         runTestOnUiThread(new Runnable() {
    447             public void run() {
    448                 a.processDraftMessage(message, true);
    449                 checkFields(RECIPIENT_TO + ", ", null, null, SUBJECT, BODY, null);
    450                 checkFocused(mMessageView);
    451             }
    452         });
    453 
    454         // if subject is null, then cursor should be there instead
    455 
    456         message.mSubject = "";
    457 
    458         runTestOnUiThread(new Runnable() {
    459             public void run() {
    460                 resetViews();
    461                 a.processDraftMessage(message, true);
    462                 checkFields(RECIPIENT_TO + ", ", null, null, null, BODY, null);
    463                 checkFocused(mSubjectView);
    464             }
    465         });
    466 
    467     }
    468 
    469     /**
    470      * Test processDraftMessage() for EDIT_DRAFT with utf-16 name and address
    471      * TODO check CC and BCC handling too
    472      */
    473     public void testProcessDraftMessageWithUtf16() throws MessagingException, Throwable {
    474 
    475         final Message message = buildTestMessage(UTF16_RECIPIENT_TO, UTF16_SENDER,
    476                 UTF16_SUBJECT, UTF16_BODY);
    477         Intent intent = new Intent(ACTION_EDIT_DRAFT);
    478         final MessageCompose a = getActivity();
    479         a.setIntent(intent);
    480 
    481         runTestOnUiThread(new Runnable() {
    482             public void run() {
    483                 a.processDraftMessage(message, true);
    484                 checkFields(UTF16_RECIPIENT_TO + ", ",
    485                         null, null, UTF16_SUBJECT, UTF16_BODY, null);
    486                 checkFocused(mMessageView);
    487             }
    488         });
    489 
    490         // if subject is null, then cursor should be there instead
    491 
    492         message.mSubject = "";
    493 
    494         runTestOnUiThread(new Runnable() {
    495             public void run() {
    496                 resetViews();
    497                 a.processDraftMessage(message, true);
    498                 checkFields(UTF16_RECIPIENT_TO + ", ", null, null, null, UTF16_BODY, null);
    499                 checkFocused(mSubjectView);
    500             }
    501         });
    502 
    503     }
    504 
    505     /**
    506      * Test processDraftMessage() for EDIT_DRAFT with utf-32 name and address
    507      * TODO check CC and BCC handling too
    508      */
    509     public void testProcessDraftMessageWithUtf32() throws MessagingException, Throwable {
    510 
    511         final Message message = buildTestMessage(UTF32_RECIPIENT_TO, UTF32_SENDER,
    512                 UTF32_SUBJECT, UTF32_BODY);
    513         Intent intent = new Intent(ACTION_EDIT_DRAFT);
    514         final MessageCompose a = getActivity();
    515         a.setIntent(intent);
    516 
    517         runTestOnUiThread(new Runnable() {
    518             public void run() {
    519                 a.processDraftMessage(message, true);
    520                 checkFields(UTF32_RECIPIENT_TO + ", ",
    521                         null, null, UTF32_SUBJECT, UTF32_BODY, null);
    522                 checkFocused(mMessageView);
    523             }
    524         });
    525 
    526         // if subject is null, then cursor should be there instead
    527 
    528         message.mSubject = "";
    529 
    530         runTestOnUiThread(new Runnable() {
    531             public void run() {
    532                 resetViews();
    533                 a.processDraftMessage(message, true);
    534                 checkFields(UTF32_RECIPIENT_TO + ", ", null, null, null, UTF32_BODY, null);
    535                 checkFocused(mSubjectView);
    536             }
    537         });
    538 
    539     }
    540 
    541     /**
    542      * Check that we create the proper to and cc addressees in reply and reply-all, making sure
    543      * to reject duplicate addressees AND the email address of the sending account
    544      *
    545      * In this case, we're doing a "reply"
    546      * The user is TO1 (a "to" recipient)
    547      * The to should be: FROM
    548      * The cc should be empty
    549      */
    550     public void testReplyAddresses() throws Throwable {
    551         final MessageCompose a = getActivity();
    552         // Doesn't matter what Intent we use here
    553         final Intent intent = new Intent(Intent.ACTION_VIEW);
    554         Message msg = new Message();
    555         final Account account = new Account();
    556 
    557         msg.mFrom = Address.parseAndPack(FROM);
    558         msg.mTo = Address.parseAndPack(TO1 + ',' + TO2);
    559         msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3);
    560         final Message message = msg;
    561         account.mEmailAddress = "FiRsT.tO (at) gOoGlE.cOm";
    562 
    563         runTestOnUiThread(new Runnable() {
    564             public void run() {
    565                 a.initFromIntent(intent);
    566                 a.setupAddressViews(message, account, false);
    567                 assertEquals("", mCcView.getText().toString());
    568                 String result = Address.parseAndPack(mToView.getText().toString());
    569                 String expected = Address.parseAndPack(FROM);
    570                 assertEquals(expected, result);
    571 
    572                 // It doesn't harm even if the CC view is visible.
    573             }
    574         });
    575     }
    576 
    577     /**
    578      * Check that we create the proper to and cc addressees in reply and reply-all, making sure
    579      * to reject duplicate addressees AND the email address of the sending account
    580      *
    581      * In this case, we're doing a "reply all"
    582      * The user is TO1 (a "to" recipient)
    583      * The to should be: FROM
    584      * The cc should be: TO2, CC1, CC2, and CC3
    585      */
    586     public void testReplyAllAddresses1() throws Throwable {
    587         final MessageCompose a = getActivity();
    588         // Doesn't matter what Intent we use here
    589         final Intent intent = new Intent(Intent.ACTION_VIEW);
    590         Message msg = new Message();
    591         final Account account = new Account();
    592 
    593         msg.mFrom = Address.parseAndPack(FROM);
    594         msg.mTo = Address.parseAndPack(TO1 + ',' + TO2);
    595         msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3);
    596         final Message message = msg;
    597         account.mEmailAddress = "FiRsT.tO (at) gOoGlE.cOm";
    598 
    599         runTestOnUiThread(new Runnable() {
    600             public void run() {
    601                 a.initFromIntent(intent);
    602                 a.setupAddressViews(message, account, true);
    603                 String result = Address.parseAndPack(mToView.getText().toString());
    604                 String expected = Address.parseAndPack(FROM);
    605                 assertEquals(expected, result);
    606                 result = Address.parseAndPack(mCcView.getText().toString());
    607                 expected = Address.parseAndPack(TO2 + ',' + CC1 + ',' + CC2 + ',' + CC3);
    608                 assertEquals(expected, result);
    609                 TestUtils.assertViewVisible(mCcView);
    610             }
    611         });
    612     }
    613 
    614     /**
    615      * Check that we create the proper to and cc addressees in reply and reply-all, making sure
    616      * to reject duplicate addressees AND the email address of the sending account
    617      *
    618      * In this case, we're doing a "reply all"
    619      * The user is CC2 (a "cc" recipient)
    620      * The to should be: FROM,
    621      * The cc should be: TO1, TO2, CC1 and CC3 (CC2 is our account's email address)
    622      */
    623     public void testReplyAllAddresses2() throws Throwable {
    624         final MessageCompose a = getActivity();
    625         // Doesn't matter what Intent we use here
    626         final Intent intent = new Intent(Intent.ACTION_VIEW);
    627         Message msg = new Message();
    628         final Account account = new Account();
    629 
    630         msg.mFrom = Address.parseAndPack(FROM);
    631         msg.mTo = Address.parseAndPack(TO1 + ',' + TO2);
    632         msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3);
    633         final Message message = msg;
    634         account.mEmailAddress = "sEcOnD.cC (at) gOoGlE.cOm";
    635 
    636         runTestOnUiThread(new Runnable() {
    637             public void run() {
    638                 a.initFromIntent(intent);
    639                 a.setupAddressViews(message, account, true);
    640                 String result = Address.parseAndPack(mToView.getText().toString());
    641                 String expected = Address.parseAndPack(FROM);
    642                 assertEquals(expected, result);
    643                 result = Address.parseAndPack(mCcView.getText().toString());
    644                 expected = Address.parseAndPack(TO1 + ',' + TO2 + ',' + CC1 + ',' + CC3);
    645                 assertEquals(expected, result);
    646                 TestUtils.assertViewVisible(mCcView);
    647             }
    648         });
    649     }
    650 
    651     /**
    652      * Check that we create the proper to and cc addressees in reply and reply-all, making sure
    653      * to reject duplicate addressees AND the email address of the sending account
    654      *
    655      * In this case, we're doing a "reply all"
    656      * The user is CC2 (a "cc" recipient)
    657      * The to should be: FROM
    658      * The cc should be: TO1, TO2, ,TO3, and CC3 (CC1/CC4 are duplicates; CC2 is the our
    659      * account's email address)
    660      */
    661     public void testReplyAllAddresses3() throws Throwable {
    662         final MessageCompose a = getActivity();
    663         // Doesn't matter what Intent we use here
    664         final Intent intent = new Intent(Intent.ACTION_VIEW);
    665         Message msg = new Message();
    666         final Account account = new Account();
    667 
    668         msg.mFrom = Address.parseAndPack(FROM);
    669         msg.mTo = Address.parseAndPack(TO1 + ',' + TO2 + ',' + TO3);
    670         msg.mCc = Address.parseAndPack(CC1 + ',' + CC2 + ',' + CC3 + ',' + CC4);
    671         final Message message = msg;
    672         account.mEmailAddress = "sEcOnD.cC (at) gOoGlE.cOm";
    673 
    674         runTestOnUiThread(new Runnable() {
    675             public void run() {
    676                 a.initFromIntent(intent);
    677                 a.setupAddressViews(message, account, true);
    678                 String result = Address.parseAndPack(mToView.getText().toString());
    679                 String expected = Address.parseAndPack(FROM);
    680                 assertEquals(expected, result);
    681                 result = Address.parseAndPack(mCcView.getText().toString());
    682                 expected = Address.parseAndPack(TO1 + ',' + TO2 + ',' + TO3+ ',' + CC3);
    683                 assertEquals(expected, result);
    684                 TestUtils.assertViewVisible(mCcView);
    685             }
    686         });
    687     }
    688 
    689     /**
    690      * Test for processing of Intent EXTRA_* fields that impact the headers:
    691      *   Intent.EXTRA_EMAIL, Intent.EXTRA_CC, Intent.EXTRA_BCC, Intent.EXTRA_SUBJECT
    692      */
    693     public void testIntentHeaderExtras() throws MessagingException, Throwable {
    694 
    695         Intent intent = new Intent(Intent.ACTION_VIEW);
    696         intent.putExtra(Intent.EXTRA_EMAIL, new String[] { RECIPIENT_TO });
    697         intent.putExtra(Intent.EXTRA_CC, new String[] { RECIPIENT_CC });
    698         intent.putExtra(Intent.EXTRA_BCC, new String[] { RECIPIENT_BCC });
    699         intent.putExtra(Intent.EXTRA_SUBJECT, SUBJECT);
    700 
    701         final MessageCompose a = getActivity();
    702         final Intent i2 = new Intent(intent);
    703 
    704         runTestOnUiThread(new Runnable() {
    705             public void run() {
    706                 a.initFromIntent(i2);
    707                 checkFields(RECIPIENT_TO + ", ", RECIPIENT_CC + ", ", RECIPIENT_BCC + ", ", SUBJECT,
    708                         null, mSignature);
    709                 checkFocused(mMessageView);
    710             }
    711         });
    712     }
    713 
    714     /**
    715      * Test for processing of Intent EXTRA_* fields that impact the headers with utf-16.
    716      */
    717     public void testIntentHeaderExtrasWithUtf16() throws MessagingException, Throwable {
    718 
    719         Intent intent = new Intent(Intent.ACTION_VIEW);
    720         intent.putExtra(Intent.EXTRA_EMAIL, new String[] { UTF16_RECIPIENT_TO });
    721         intent.putExtra(Intent.EXTRA_CC, new String[] { UTF16_RECIPIENT_CC });
    722         intent.putExtra(Intent.EXTRA_BCC, new String[] { UTF16_RECIPIENT_BCC });
    723         intent.putExtra(Intent.EXTRA_SUBJECT, UTF16_SUBJECT);
    724 
    725         final MessageCompose a = getActivity();
    726         final Intent i2 = new Intent(intent);
    727 
    728         runTestOnUiThread(new Runnable() {
    729             public void run() {
    730                 a.initFromIntent(i2);
    731                 checkFields(UTF16_RECIPIENT_TO + ", ", UTF16_RECIPIENT_CC + ", ",
    732                         UTF16_RECIPIENT_BCC + ", ", UTF16_SUBJECT, null, mSignature);
    733                 checkFocused(mMessageView);
    734             }
    735         });
    736     }
    737 
    738     /**
    739      * Test for processing of Intent EXTRA_* fields that impact the headers with utf-32.
    740      */
    741     public void testIntentHeaderExtrasWithUtf32() throws MessagingException, Throwable {
    742 
    743         Intent intent = new Intent(Intent.ACTION_VIEW);
    744         intent.putExtra(Intent.EXTRA_EMAIL, new String[] { UTF32_RECIPIENT_TO });
    745         intent.putExtra(Intent.EXTRA_CC, new String[] { UTF32_RECIPIENT_CC });
    746         intent.putExtra(Intent.EXTRA_BCC, new String[] { UTF32_RECIPIENT_BCC });
    747         intent.putExtra(Intent.EXTRA_SUBJECT, UTF32_SUBJECT);
    748 
    749         final MessageCompose a = getActivity();
    750         final Intent i2 = new Intent(intent);
    751 
    752         runTestOnUiThread(new Runnable() {
    753             public void run() {
    754                 a.initFromIntent(i2);
    755                 checkFields(UTF32_RECIPIENT_TO + ", ", UTF32_RECIPIENT_CC + ", ",
    756                         UTF32_RECIPIENT_BCC + ", ", UTF32_SUBJECT, null, mSignature);
    757                 checkFocused(mMessageView);
    758             }
    759         });
    760     }
    761 
    762     /**
    763      * Test for processing of a typical browser "share" intent, e.g.
    764      * type="text/plain", EXTRA_TEXT="http:link.server.com"
    765      */
    766     public void testIntentSendPlainText() throws MessagingException, Throwable {
    767 
    768         Intent intent = new Intent(Intent.ACTION_SEND);
    769         intent.setType("text/plain");
    770         intent.putExtra(Intent.EXTRA_TEXT, BODY);
    771 
    772         final MessageCompose a = getActivity();
    773         final Intent i2 = new Intent(intent);
    774 
    775         runTestOnUiThread(new Runnable() {
    776             public void run() {
    777                 a.initFromIntent(i2);
    778                 checkFields(null, null, null, null, BODY, mSignature);
    779                 checkFocused(mToView);
    780             }
    781         });
    782     }
    783 
    784     /**
    785      * Test for processing of a typical browser Mailto intent, e.g.
    786      * action=android.intent.action.VIEW
    787      * categories={android.intent.category.BROWSABLE}
    788      * data=mailto:user (at) domain.com?subject=This%20is%20%the%subject
    789      */
    790     public void testBrowserMailToIntent() throws MessagingException, Throwable {
    791 
    792         Intent intent = new Intent(Intent.ACTION_VIEW);
    793         Uri uri = Uri.parse("mailto:" + RECIPIENT_TO + "?subject=This%20is%20the%20subject");
    794         intent.setData(uri);
    795 
    796         final MessageCompose a = getActivity();
    797         final Intent i2 = new Intent(intent);
    798 
    799         runTestOnUiThread(new Runnable() {
    800             public void run() {
    801                 a.initFromIntent(i2);
    802                 checkFields(
    803                         RECIPIENT_TO + ", ", null, null, "This is the subject", null, mSignature);
    804                 checkFocused(mMessageView);
    805             }
    806         });
    807     }
    808 
    809     /**
    810      * TODO: test mailto: with simple encoding mode
    811      * TODO: test mailto: URI with all optional fields
    812      * TODO: come up with a way to add a very small attachment
    813      * TODO: confirm the various details between handling of SEND, VIEW, SENDTO
    814      */
    815 
    816     /**
    817      * Helper method to quickly check (and assert) on the to, subject, and content views.
    818      *
    819      * @param to expected value (null = it must be empty)
    820      * @param cc expected value (null = it must be empty)
    821      * @param bcc expected value (null = it must be empty)
    822      * @param subject expected value (null = it must be empty)
    823      * @param content expected value (null = it must be empty)
    824      * @param signature expected value (null = it must be empty)
    825      */
    826     private void checkFields(String to, String cc, String bcc, String subject, String content,
    827             String signature) {
    828         String toText = mToView.getText().toString();
    829         if (to == null) {
    830             assertEquals(0, toText.length());
    831         } else {
    832             assertEquals(to, toText);
    833             TestUtils.assertViewVisible(mToView);
    834         }
    835 
    836         String ccText = mCcView.getText().toString();
    837         if (cc == null) {
    838             assertEquals(0, ccText.length());
    839         } else {
    840             assertEquals(cc, ccText);
    841             TestUtils.assertViewVisible(mCcView);
    842         }
    843 
    844         String bccText = mBccView.getText().toString();
    845         if (bcc == null) {
    846             assertEquals(0, bccText.length());
    847         } else {
    848             assertEquals(bcc, bccText);
    849             TestUtils.assertViewVisible(mBccView);
    850         }
    851 
    852         String subjectText = mSubjectView.getText().toString();
    853         if (subject == null) {
    854             assertEquals(0, subjectText.length());
    855         } else {
    856             assertEquals(subject, subjectText);
    857         }
    858 
    859         String contentText = mMessageView.getText().toString();
    860         if (content == null && signature == null) {
    861             assertEquals(0, contentText.length());
    862         } else {
    863             if (content == null) content = "";
    864             if (signature != null) {
    865                 int textLength = content.length();
    866                 if (textLength == 0 || content.charAt(textLength - 1) != '\n') {
    867                     content += "\n";
    868                 }
    869                 content += signature;
    870             }
    871             assertEquals(content, contentText);
    872         }
    873     }
    874 
    875     /**
    876      * Helper method to verify which field has the focus
    877      * @param focused The view that should be focused (all others should not have focus)
    878      */
    879     private void checkFocused(View focused) {
    880         assertEquals(focused == mToView, mToView.isFocused());
    881         assertEquals(focused == mSubjectView, mSubjectView.isFocused());
    882         assertEquals(focused == mMessageView, mMessageView.isFocused());
    883     }
    884 
    885     /**
    886      * Helper used when running multiple calls to processSourceMessage within a test method.
    887      * Simply clears out the views, so that we get fresh data and not appended data.
    888      *
    889      * Must call from UI thread.
    890      */
    891     private void resetViews() {
    892         mToView.setText(null);
    893         mSubjectView.setText(null);
    894         mMessageView.setText(null);
    895     }
    896 
    897     /**
    898      * Build a test message that can be used as input to processSourceMessage
    899      *
    900      * @param to Recipient(s) of the message
    901      * @param sender Sender(s) of the message
    902      * @param subject Subject of the message
    903      * @param content Content of the message
    904      * @return a complete Message object
    905      */
    906     private Message buildTestMessage(String to, String sender, String subject, String content) {
    907         Message message = new Message();
    908 
    909         if (to != null) {
    910             message.mTo = Address.parseAndPack(to);
    911         }
    912 
    913         if (sender != null) {
    914             Address[] addresses = Address.parse(sender);
    915             assertTrue("from address", addresses.length > 0);
    916             message.mFrom = addresses[0].pack();
    917         }
    918 
    919         message.mSubject = subject;
    920 
    921         if (content != null) {
    922             message.mText = content;
    923         }
    924 
    925         return message;
    926     }
    927 
    928     /**
    929      * Check AddressTextView email address validation.
    930      */
    931     @UiThreadTest
    932     public void testAddressTextView() {
    933         MessageCompose messageCompose = getActivity();
    934 
    935         mToView.setValidator(new EmailAddressValidator());
    936         mToView.setText("foo");
    937         mToView.performValidation();
    938 
    939         // address is validated as errorneous
    940         assertFalse(messageCompose.isAddressAllValid());
    941 
    942         // the wrong address is preserved by validation
    943         assertEquals("foo", mToView.getText().toString());
    944 
    945         mToView.setText("a (at) b.c");
    946         mToView.performValidation();
    947 
    948         // address is validated as correct
    949         assertTrue(messageCompose.isAddressAllValid());
    950 
    951         mToView.setText("a (at) b.c, foo");
    952         mToView.performValidation();
    953 
    954         assertFalse(messageCompose.isAddressAllValid());
    955         assertEquals("a (at) b.c, foo", mToView.getText().toString());
    956     }
    957 
    958     /**
    959      * Check message and selection with/without signature.
    960      */
    961     public void testSetInitialComposeTextAndSelection() throws MessagingException, Throwable {
    962         final Message msg = buildTestMessage(null, null, null, BODY);
    963         final Intent intent = new Intent(ACTION_EDIT_DRAFT);
    964         final Account account = new Account();
    965         final MessageCompose a = getActivity();
    966         a.setIntent(intent);
    967 
    968         runTestOnUiThread(new Runnable() {
    969             public void run() {
    970                 resetViews();
    971                 a.setInitialComposeText(BODY, SIGNATURE);
    972                 checkFields(null, null, null, null, BODY, SIGNATURE);
    973                 a.setMessageContentSelection(SIGNATURE);
    974                 assertEquals(BODY.length(), mMessageView.getSelectionStart());
    975             }
    976         });
    977 
    978         runTestOnUiThread(new Runnable() {
    979             public void run() {
    980                 resetViews();
    981                 a.setInitialComposeText(BODY, null);
    982                 checkFields(null, null, null, null, BODY, null);
    983                 a.setMessageContentSelection(null);
    984                 assertEquals(BODY.length(), mMessageView.getSelectionStart());
    985             }
    986         });
    987 
    988         runTestOnUiThread(new Runnable() {
    989             public void run() {
    990                 resetViews();
    991                 final String body2 = BODY + "\n\na\n\n";
    992                 a.setInitialComposeText(body2, SIGNATURE);
    993                 checkFields(null, null, null, null, body2, SIGNATURE);
    994                 a.setMessageContentSelection(SIGNATURE);
    995                 assertEquals(BODY.length() + 3, mMessageView.getSelectionStart());
    996             }
    997         });
    998 
    999     }
   1000 
   1001     private static int sAttachmentId = 1;
   1002     private Attachment makeAttachment(String filename) {
   1003         Attachment a = new Attachment();
   1004         a.mId = sAttachmentId++;
   1005         a.mFileName = filename;
   1006         return a;
   1007     }
   1008 
   1009     @SmallTest
   1010     public void testSourceAttachmentsProcessing() {
   1011         // Attachments currently in the draft.
   1012         ArrayList<Attachment> currentAttachments = Lists.newArrayList(
   1013                 makeAttachment("a.png"), makeAttachment("b.png"));
   1014 
   1015         // Attachments in the message being forwarded.
   1016         Attachment c = makeAttachment("c.png");
   1017         Attachment d = makeAttachment("d.png");
   1018         ArrayList<Attachment> sourceAttachments = Lists.newArrayList(c, d);
   1019 
   1020         // Ensure the source attachments gets added.
   1021         final MessageCompose a = getActivity();
   1022         a.processSourceMessageAttachments(currentAttachments, sourceAttachments, true /*include*/);
   1023 
   1024         assertEquals(4, currentAttachments.size());
   1025         assertTrue(currentAttachments.contains(c));
   1026         assertTrue(currentAttachments.contains(d));
   1027 
   1028         // Now ensure they can be removed (e.g. in the case of switching from forward to reply).
   1029         a.processSourceMessageAttachments(currentAttachments, sourceAttachments, false /*include*/);
   1030         assertEquals(2, currentAttachments.size());
   1031         assertFalse(currentAttachments.contains(c));
   1032         assertFalse(currentAttachments.contains(d));
   1033     }
   1034 }
   1035