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