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