Always show the more chip for a collapsed recipient view if there are too many recipients.
Previously, this would not get shrunk until the user focused/ unfocusd
the field. Now, if the field doesn't have focus, it starts out
in the shrunk mode.
Also, when appending a contact, properly tokenizes the contact
so that we show just the email address.
Change-Id: I085ed00dc94b38d22e871d7e80589e5b2d74f98f
diff --git a/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index 792fdcc..2e89324 100644
--- a/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -54,8 +54,10 @@
@Override
public long getItemId(int position) {
Cursor c = getCursor();
- c.moveToPosition(position);
- return c.getLong(EmailQuery.DATA_ID);
+ if (c.moveToPosition(position)) {
+ c.getLong(EmailQuery.DATA_ID);
+ }
+ return -1;
}
public RecipientEntry getRecipientEntry(int position) {
diff --git a/src/com/android/ex/chips/RecipientEditTextView.java b/src/com/android/ex/chips/RecipientEditTextView.java
index 06d60a7..0000a8c 100644
--- a/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/src/com/android/ex/chips/RecipientEditTextView.java
@@ -37,6 +37,8 @@
import android.text.TextWatcher;
import android.text.method.QwertyKeyListener;
import android.text.style.ImageSpan;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
@@ -100,7 +102,6 @@
private int mMoreString;
- private ArrayList<RecipientChip> mRemovedSpans;
private final ArrayList<String> mPendingChips = new ArrayList<String>();
@@ -130,6 +131,8 @@
private ListPopupWindow mAlternatesPopup;
+ private ArrayList<RecipientChip> mRemovedSpans;
+
/**
* Used with {@link mAlternatesPopup}. Handles clicks to alternate addresses for a selected chip.
*/
@@ -197,7 +200,7 @@
// cause any needed services to be started and make the first filter
// query come back more quickly.
Filter f = ((Filterable) adapter).getFilter();
- f.filter(null);
+ f.filter("xxxxxxxxxxxx");
}
@Override
@@ -258,7 +261,7 @@
} else {
commitDefault();
}
- mMoreChip = createMoreChip();
+ createMoreChip();
}
private void expand() {
@@ -468,25 +471,7 @@
// on the view.
if (width != 0 && height != 0) {
if (mPendingChipsCount > 0) {
- Editable editable = getText();
- // Tokenize!
- for (int i = 0; i < mPendingChips.size(); i++) {
- String current = mPendingChips.get(i);
- int tokenStart = editable.toString().indexOf(current);
- int tokenEnd = tokenStart + current.length();
- if (tokenStart >= 0) {
- // When we have a valid token, include it with the token
- // to the left.
- if (tokenEnd < editable.length() - 2
- && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
- tokenEnd++;
- }
- String token = editable.toString().substring(tokenStart, tokenEnd);
- editable.replace(tokenStart, tokenEnd, createChip(RecipientEntry
- .constructFakeEntry(token), false));
- }
- }
- mPendingChipsCount--;
+ handlePendingChips();
}
mPendingChipsCount = 0;
mPendingChips.clear();
@@ -504,6 +489,98 @@
}
}
+ private void handlePendingChips() {
+ Editable editable = getText();
+ // Tokenize!
+ for (int i = 0; i < mPendingChips.size(); i++) {
+ String current = mPendingChips.get(i);
+ int tokenStart = editable.toString().indexOf(current);
+ int tokenEnd = tokenStart + current.length();
+ if (tokenStart >= 0) {
+ // When we have a valid token, include it with the token
+ // to the left.
+ if (tokenEnd < editable.length() - 2
+ && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
+ tokenEnd++;
+ }
+ createReplacementChip(tokenStart, tokenEnd, editable);
+ }
+ mPendingChipsCount--;
+ }
+ sanitizeSpannable();
+ if (!hasFocus()) {
+ createMoreChip();
+ }
+ }
+
+ /**
+ * Remove any characters after the last valid chip.
+ */
+ private void sanitizeSpannable() {
+ // Find the last chip; eliminate any commit characters after it.
+ RecipientChip[] chips = getRecipients();
+ if (chips != null && chips.length > 0) {
+ int end;
+ ImageSpan lastSpan;
+ if (mMoreChip != null) {
+ lastSpan = mMoreChip;
+ } else {
+ lastSpan = chips[chips.length - 1];
+ }
+ end = getSpannable().getSpanEnd(lastSpan);
+ Editable editable = getText();
+ int length = editable.length();
+ if (length > end) {
+ // See what characters occur after that and eliminate them.
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "There were extra characters after the last tokenizable entry."
+ + editable);
+ }
+ editable.delete(end + 1, length);
+ }
+ }
+ }
+
+ /**
+ * Create a chip that represents just the email address of a recipient. At some later
+ * point, this chip will be attached to a real contact entry, if one exists.
+ */
+ private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
+ String token = editable.toString().substring(tokenStart, tokenEnd).trim();
+ int commitCharIndex = token.indexOf(COMMIT_CHAR_COMMA);
+ if (commitCharIndex == token.length() - 1) {
+ token = token.substring(0, token.length() - 1);
+ }
+ RecipientEntry entry = createTokenizedEntry(token);
+ String displayText = entry.getDestination();
+ displayText = (String) mTokenizer.terminateToken(displayText);
+ // Always leave a blank space at the end of a chip.
+ int textLength = displayText.length() - 1;
+ SpannableString chipText = new SpannableString(displayText);
+ int end = getSelectionEnd();
+ int start = mTokenizer.findTokenStart(getText(), end);
+ RecipientChip chip = null;
+ try {
+ chip = constructChipSpan(entry, start, false);
+ chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } catch (NullPointerException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+
+ editable.replace(tokenStart, tokenEnd, chipText);
+ }
+
+ private RecipientEntry createTokenizedEntry(String token) {
+ Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
+ String address;
+ if (tokens != null && tokens.length > 0) {
+ address = tokens[0].getAddress();
+ } else {
+ address = token;
+ }
+ return RecipientEntry.constructFakeEntry(address);
+ }
+
@Override
public void setTokenizer(Tokenizer tokenizer) {
mTokenizer = tokenizer;
@@ -786,9 +863,8 @@
}
private void scrollLineIntoView(int line) {
- int scrollBy = calculateOffsetFromBottom(line, (int)mChipHeight);
if (mScrollView != null) {
- mScrollView.scrollBy(0, scrollBy);
+ mScrollView.scrollBy(0, calculateOffsetFromBottom(line, (int) mChipHeight));
}
}
@@ -910,15 +986,8 @@
}
private void submitItemAtPosition(int position) {
- RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position);
- // If the display name and the address are the same, or if this is a
- // valid contact, but the destination is invalid, then make this a fake
- // recipient that is editable.
- String destination = entry.getDestination();
- if (TextUtils.equals(entry.getDisplayName(), destination)
- || (mValidator != null && !mValidator.isValid(destination))) {
- entry = RecipientEntry.constructFakeEntry(destination);
- }
+ RecipientEntry entry = createValidatedEntry(
+ (RecipientEntry)getAdapter().getItem(position));
clearComposingText();
int end = getSelectionEnd();
@@ -929,6 +998,24 @@
editable.replace(start, end, createChip(entry, false));
}
+ private RecipientEntry createValidatedEntry(RecipientEntry item) {
+ if (item == null) {
+ return null;
+ }
+ final RecipientEntry entry;
+ // If the display name and the address are the same, or if this is a
+ // valid contact, but the destination is invalid, then make this a fake
+ // recipient that is editable.
+ String destination = item.getDestination();
+ if (TextUtils.equals(item.getDisplayName(), destination)
+ || (mValidator != null && !mValidator.isValid(destination))) {
+ entry = RecipientEntry.constructFakeEntry(destination);
+ } else {
+ entry = item;
+ }
+ return entry;
+ }
+
/** Returns a collection of contact Id for each chip inside this View. */
/* package */ Collection<Long> getContactIds() {
final Set<Long> result = new HashSet<Long>();
@@ -985,10 +1072,11 @@
* do not fit in the pre-defined available space when the
* RecipientEditTextView loses focus.
*/
- private ImageSpan createMoreChip() {
+ private void createMoreChip() {
RecipientChip[] recipients = getRecipients();
if (recipients == null || recipients.length <= CHIP_LIMIT) {
- return null;
+ mMoreChip = null;
+ return;
}
int numRecipients = recipients.length;
int overage = numRecipients - CHIP_LIMIT;
@@ -1009,8 +1097,9 @@
// Remove the overage chips.
if (recipients == null || recipients.length == 0) {
Log.w(TAG,
- "We have recipients. Tt should not be possible to have zero RecipientChips.");
- return null;
+ "We have recipients. Tt should not be possible to have zero RecipientChips.");
+ mMoreChip = null;
+ return;
}
mRemovedSpans = new ArrayList<RecipientChip>();
int totalReplaceStart = 0;
@@ -1031,7 +1120,7 @@
totalReplaceEnd));
chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
text.replace(totalReplaceStart, totalReplaceEnd, chipText);
- return moreSpan;
+ mMoreChip = moreSpan;
}
/**
@@ -1050,7 +1139,16 @@
SpannableString associatedText;
for (RecipientChip chip : mRemovedSpans) {
int chipStart = chip.getStoredChipStart();
- int chipEnd = Math.min(editable.length(), chip.getStoredChipEnd());
+ int chipEnd;
+ String token;
+ if (chipStart == -1) {
+ // Need to find the location of the chip, again.
+ token = (String)mTokenizer.terminateToken(chip.getEntry().getDestination());
+ chipStart = editable.toString().indexOf(token);
+ chipEnd = chipStart + token.length();
+ } else {
+ chipEnd = Math.min(editable.length(), chip.getStoredChipEnd());
+ }
if (Log.isLoggable(TAG, Log.DEBUG) && chipEnd != chip.getStoredChipEnd()) {
Log.d(TAG,
"Unexpectedly, the chip ended after the end of the editable text. "
@@ -1292,5 +1390,4 @@
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
}
-
}
\ No newline at end of file