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