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