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