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