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