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