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:
parent
dd46221bd8
commit
ed0e683d86
|
@ -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"
|
||||
|
|
|
@ -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. -->
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1938,4 +1938,4 @@ public class FolderMessageList extends ExpandableListActivity {
|
|||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(""));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue