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