Don't drop invalid addresses while editing & check that all addresses are valid before send.

BUG: 1542391
add unit tests.
This commit is contained in:
Mihai Preda 2009-06-19 15:53:10 -07:00
parent dd46221bd8
commit ed0e683d86
8 changed files with 198 additions and 24 deletions

View File

@ -26,7 +26,7 @@
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content" android:background="#ededed">
<MultiAutoCompleteTextView
<com.android.email.activity.AddressTextView
android:id="@+id/to" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
@ -36,7 +36,7 @@
android:inputType="textEmailAddress|textMultiLine"
android:imeOptions="actionNext"
android:hint="@string/message_compose_to_hint" />
<MultiAutoCompleteTextView
<com.android.email.activity.AddressTextView
android:id="@+id/cc" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
@ -47,7 +47,7 @@
android:imeOptions="actionNext"
android:hint="@string/message_compose_cc_hint"
android:visibility="gone" />
<MultiAutoCompleteTextView
<com.android.email.activity.AddressTextView
android:id="@+id/bcc" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"

View File

@ -172,6 +172,8 @@
<string name="message_compose_quoted_text_label">Quoted text</string>
<!-- Toast that appears if you try to send with no recipients. -->
<string name="message_compose_error_no_recipients">You must add at least one recipient.</string>
<!-- An address field contains invalid email addresses. -->
<string name="message_compose_error_invalid_email">Some email addresses are invalid.</string>
<!-- Toast that appears in the context of forwarding a message with attachment(s) -->
<string name="message_compose_attachments_skipped_toast">Some attachments cannot be forwarded because they have not downloaded.</string>
<!-- Toast that appears when an attachment is too big to send. -->

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import android.widget.AutoCompleteTextView.Validator;
import android.widget.MultiAutoCompleteTextView;
import android.view.KeyEvent;
import android.content.Context;
import android.util.AttributeSet;
import android.graphics.drawable.Drawable;
import android.graphics.Rect;
import com.android.email.R;
/**
* This is a MultiAutoCompleteTextView which sets the error state
* (@see TextView.setError) when email address validation fails.
*/
class AddressTextView extends MultiAutoCompleteTextView {
private class ForwardValidator implements Validator {
private Validator mValidator = null;
public CharSequence fixText(CharSequence invalidText) {
mIsValid = false;
return invalidText;
}
public boolean isValid(CharSequence text) {
return mValidator != null ? mValidator.isValid(text) : true;
}
public void setValidator(Validator validator) {
mValidator = validator;
}
}
private boolean mIsValid = true;
private ForwardValidator mInternalValidator = new ForwardValidator();
public AddressTextView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setValidator(mInternalValidator);
}
@Override
public void setValidator(Validator validator) {
mInternalValidator.setValidator(validator);
}
@Override
public void performValidation() {
mIsValid = true;
super.performValidation();
markError(!mIsValid);
}
private void markError(boolean enable) {
if (enable) {
setError(getContext().getString(R.string.message_compose_error_invalid_email));
} else {
setError(null);
}
}
}

View File

@ -1938,4 +1938,4 @@ public class FolderMessageList extends ExpandableListActivity {
}
}
}

View File

@ -577,7 +577,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
view.append(address + ", ");
}
private Address[] getAddresses(MultiAutoCompleteTextView view) {
private Address[] getAddresses(TextView view) {
Address[] addresses = Address.parse(view.getText().toString().trim());
return addresses;
}
@ -744,18 +744,35 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
sendOrSaveMessage(true);
}
/**
* Checks whether all the email addresses listed in TO, CC, BCC are valid.
*/
/* package */ boolean isAddressAllValid() {
for (TextView view : new TextView[]{mToView, mCcView, mBccView}) {
String addresses = view.getText().toString().trim();
if (!Address.isAllValid(addresses)) {
view.setError(getString(R.string.message_compose_error_invalid_email));
return false;
}
}
return true;
}
private void onSend() {
if (getAddresses(mToView).length == 0 &&
if (!isAddressAllValid()) {
Toast.makeText(this, getString(R.string.message_compose_error_invalid_email),
Toast.LENGTH_LONG).show();
} else if (getAddresses(mToView).length == 0 &&
getAddresses(mCcView).length == 0 &&
getAddresses(mBccView).length == 0) {
mToView.setError(getString(R.string.message_compose_error_no_recipients));
Toast.makeText(this, getString(R.string.message_compose_error_no_recipients),
Toast.LENGTH_LONG).show();
return;
} else {
sendOrSaveMessage(false);
mDraftNeedsSaving = false;
finish();
}
sendOrSaveMessage(false);
mDraftNeedsSaving = false;
finish();
}
private void onDiscard() {

View File

@ -60,6 +60,8 @@ public class Address {
// Regex that matches escaped character '\\([\\"])'
private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
public Address(String address, String personal) {
setAddress(address);
setPersonal(personal);
@ -104,6 +106,26 @@ public class Address {
this.mPersonal = personal;
}
/**
* This method is used to check that all the addresses that the user
* entered in a list (e.g. To:) are valid, so that none is dropped.
*/
public static boolean isAllValid(String addressList) {
// This code mimics the parse() method below.
// I don't know how to better avoid the code-duplication.
if (addressList != null && addressList.length() > 0) {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
for (int i = 0, length = tokens.length; i < length; ++i) {
Rfc822Token token = tokens[i];
String address = token.getAddress();
if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
return false;
}
}
}
return true;
}
/**
* Parse a comma-delimited list of addresses in RFC822 format and return an
* array of Address objects.
@ -113,7 +135,7 @@ public class Address {
*/
public static Address[] parse(String addressList) {
if (addressList == null || addressList.length() == 0) {
return new Address[] {};
return EMPTY_ADDRESS_ARRAY;
}
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
ArrayList<Address> addresses = new ArrayList<Address>();
@ -121,16 +143,7 @@ public class Address {
Rfc822Token token = tokens[i];
String address = token.getAddress();
if (!TextUtils.isEmpty(address)) {
// Note: Some email provider may violate the standard, so here we only check that
// address consists of two part that are separated by '@', and domain part contains
// at least one '.'.
int len = address.length();
int firstAt = address.indexOf('@');
int lastAt = address.lastIndexOf('@');
int firstDot = address.indexOf('.', lastAt + 1);
int lastDot = address.lastIndexOf('.');
if (firstAt > 0 && firstAt == lastAt && lastAt + 1 < firstDot
&& firstDot <= lastDot && lastDot < len - 1) {
if (isValidAddress(address)) {
String name = token.getName();
if (TextUtils.isEmpty(name)) {
name = null;
@ -142,6 +155,23 @@ public class Address {
return addresses.toArray(new Address[] {});
}
/**
* Checks whether a string email address is valid.
* E.g. name@domain.com is valid.
*/
/* package */ static boolean isValidAddress(String address) {
// Note: Some email provider may violate the standard, so here we only check that
// address consists of two part that are separated by '@', and domain part contains
// at least one '.'.
int len = address.length();
int firstAt = address.indexOf('@');
int lastAt = address.lastIndexOf('@');
int firstDot = address.indexOf('.', lastAt + 1);
int lastDot = address.lastIndexOf('.');
return firstAt > 0 && firstAt == lastAt && lastAt + 1 < firstDot
&& firstDot <= lastDot && lastDot < len - 1;
}
@Override
public boolean equals(Object o) {
if (o instanceof Address) {

View File

@ -18,6 +18,7 @@ package com.android.email.activity;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.EmailAddressValidator;
import com.android.email.mail.Address;
import com.android.email.mail.Message;
import com.android.email.mail.MessagingException;
@ -31,8 +32,10 @@ import android.content.Intent;
import android.net.Uri;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.UiThreadTest;
import android.view.View;
import android.widget.EditText;
import android.widget.AutoCompleteTextView;
/**
* Various instrumentation tests for MessageCompose.
@ -43,7 +46,7 @@ import android.widget.EditText;
public class MessageComposeInstrumentationTests
extends ActivityInstrumentationTestCase2<MessageCompose> {
private EditText mToView;
private AutoCompleteTextView mToView;
private EditText mSubjectView;
private EditText mMessageView;
@ -107,7 +110,7 @@ public class MessageComposeInstrumentationTests
Intent intent = new Intent(Intent.ACTION_VIEW);
setActivityIntent(intent);
final MessageCompose a = getActivity();
mToView = (EditText) a.findViewById(R.id.to);
mToView = (AutoCompleteTextView) a.findViewById(R.id.to);
mSubjectView = (EditText) a.findViewById(R.id.subject);
mMessageView = (EditText) a.findViewById(R.id.message_content);
}
@ -574,6 +577,39 @@ public class MessageComposeInstrumentationTests
return message;
}
/**
* Check AddressTextView email address validation.
*/
@UiThreadTest
public void testAddressTextView() {
MessageCompose messageCompose = getActivity();
mToView.setValidator(new EmailAddressValidator());
mToView.setText("foo");
mToView.performValidation();
// address is validated as errorneous
assertNotNull(mToView.getError());
assertFalse(messageCompose.isAddressAllValid());
// the wrong address is preserved by validation
assertEquals("foo, ", mToView.getText().toString());
mToView.setText("a@b.c");
mToView.performValidation();
// address is validated as correct
assertNull(mToView.getError());
assertTrue(messageCompose.isAddressAllValid());
mToView.setText("a@b.c, foo");
mToView.performValidation();
assertNotNull(mToView.getError());
assertFalse(messageCompose.isAddressAllValid());
assertEquals("a@b.c, foo, ", mToView.getText().toString());
}
/**
* Tests for the comma-inserting logic. The logic is applied equally to To: Cc: and Bcc:
* but we only run the full set on To:
@ -623,5 +659,4 @@ public class MessageComposeInstrumentationTests
assertEquals(expect, result);
}
}

View File

@ -512,4 +512,17 @@ public class AddressUnitTests extends AndroidTestCase {
assertTrue("unpacking zero-length", result != null && result.length == 0);
}
public void testIsValidAddress() {
String notValid[] = {"", "foo", "john@", "x@y", "x@y.", "foo.com"};
String valid[] = {"x@y.z", "john@gmail.com", "a@b.c.d"};
for (String address : notValid) {
assertTrue(address, !Address.isValidAddress(address));
}
for (String address : valid) {
assertTrue(address, Address.isValidAddress(address));
}
// isAllValid() must accept empty address list as valid
assertTrue("Empty address list is valid", Address.isAllValid(""));
}
}