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