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