Do reverse lookups on pre-appended recipient chips.
Change-Id: I338a07f89264606a58942520791f39908fedbedb
diff --git a/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index 2e89324..d92fecd 100644
--- a/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -21,6 +21,8 @@
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -28,7 +30,10 @@
import android.widget.ImageView;
import android.widget.TextView;
+import java.util.HashMap;
+
public class RecipientAlternatesAdapter extends CursorAdapter {
+ static final int MAX_LOOKUPS = 50;
private final LayoutInflater mLayoutInflater;
private final int mLayoutId;
@@ -39,6 +44,53 @@
private OnCheckedItemChangedListener mCheckedItemChangedListener;
+ /**
+ * Get a HashMap of address to RecipientEntry that contains all contact
+ * information for a contact with the provided address, if one exists. This
+ * may block the UI, so run it in an async task.
+ *
+ * @param context Context.
+ * @param addresses Array of addresses on which to perform the lookup.
+ * @return HashMap<String,RecipientEntry>
+ */
+ public static HashMap<String, RecipientEntry> getMatchingRecipients(Context context,
+ String[] inAddresses) {
+ int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.length);
+ String[] addresses = new String[addressesSize];
+ StringBuilder bindString = new StringBuilder();
+ // Create the "?" string and set up arguments.
+ for (int i = 0; i < addressesSize; i++) {
+ Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses[i]);
+ addresses[i] = (tokens.length > 0 ? tokens[0].getAddress() : inAddresses[i]);
+ bindString.append("?");
+ if (i < addressesSize - 1) {
+ bindString.append(",");
+ }
+ }
+
+ Cursor c = context.getContentResolver().query(Email.CONTENT_URI, EmailQuery.PROJECTION,
+ Email.ADDRESS + " IN (" + bindString.toString() + ")", addresses, null);
+ HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
+ if (c != null) {
+ try {
+ if (c.moveToFirst()) {
+ do {
+ String address = c.getString(EmailQuery.ADDRESS);
+ recipientEntries.put(address, RecipientEntry.constructTopLevelEntry(
+ c.getString(EmailQuery.NAME),
+ c.getString(EmailQuery.ADDRESS),
+ c.getLong(EmailQuery.CONTACT_ID),
+ c.getLong(EmailQuery.DATA_ID),
+ c.getString(EmailQuery.PHOTO_THUMBNAIL_URI)));
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
+ }
+ }
+ return recipientEntries;
+ }
+
public RecipientAlternatesAdapter(Context context, long contactId, long currentId, int viewId,
OnCheckedItemChangedListener listener) {
super(context, context.getContentResolver().query(Email.CONTENT_URI, EmailQuery.PROJECTION,
diff --git a/src/com/android/ex/chips/RecipientEditTextView.java b/src/com/android/ex/chips/RecipientEditTextView.java
index 0000a8c..67dafa4 100644
--- a/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/src/com/android/ex/chips/RecipientEditTextView.java
@@ -25,12 +25,14 @@
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
@@ -60,6 +62,7 @@
import android.widget.ScrollView;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@@ -131,6 +134,8 @@
private ListPopupWindow mAlternatesPopup;
+ private ArrayList<RecipientChip> mTemporaryRecipients;
+
private ArrayList<RecipientChip> mRemovedSpans;
/**
@@ -139,7 +144,6 @@
private OnItemClickListener mAlternatesListener;
private int mCheckedItem;
-
private TextWatcher mTextWatcher;
private ScrollView mScrollView;
@@ -156,6 +160,8 @@
}
};
+ private IndividualReplacementTask mIndividualReplacements;
+
public RecipientEditTextView(Context context, AttributeSet attrs) {
super(context, attrs);
if (sSelectedTextColor == -1) {
@@ -200,7 +206,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("xxxxxxxxxxxx");
+ f.filter("sara");
}
@Override
@@ -269,6 +275,12 @@
setCursorVisible(true);
Editable text = getText();
setSelection(text != null && text.length() > 0 ? text.length() : 0);
+ // If there are any temporary chips, try replacing them now that the user
+ // has expanded the field.
+ if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
+ new RecipientReplacementTask().execute();
+ mTemporaryRecipients = null;
+ }
}
private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
@@ -477,6 +489,7 @@
mPendingChips.clear();
mHandler.post(mAddTextWatcher);
}
+ // Try to find the scroll view parent, if it exists.
if (mScrollView == null && !mTried) {
ViewParent parent = getParent();
while (parent != null && !(parent instanceof ScrollView)) {
@@ -490,6 +503,7 @@
}
private void handlePendingChips() {
+ mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount);
Editable editable = getText();
// Tokenize!
for (int i = 0; i < mPendingChips.size(); i++) {
@@ -508,7 +522,24 @@
mPendingChipsCount--;
}
sanitizeSpannable();
- if (!hasFocus()) {
+ if (mTemporaryRecipients != null
+ && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
+ if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
+ new RecipientReplacementTask().execute();
+ mTemporaryRecipients = null;
+ } else {
+ // Create the "more" chip
+ mIndividualReplacements = new IndividualReplacementTask();
+ mIndividualReplacements.execute(new ArrayList<RecipientChip>(mTemporaryRecipients
+ .subList(0, CHIP_LIMIT)));
+
+ createMoreChip();
+ }
+ } else {
+ // There are too many recipients to look up, so just fall back to
+ // showing addresses
+ // for all of them.
+ mTemporaryRecipients = null;
createMoreChip();
}
}
@@ -568,6 +599,10 @@
}
editable.replace(tokenStart, tokenEnd, chipText);
+ // Add this chip to the list of entries "to replace"
+ if (chip != null) {
+ mTemporaryRecipients.add(chip);
+ }
}
private RecipientEntry createTokenizedEntry(String token) {
@@ -874,8 +909,8 @@
int bottom = calculateOffsetFromBottom(line, (int) mChipHeight);
// Align the alternates popup with the left side of the View,
// regardless of the position of the chip tapped.
- alternatesPopup.setAnchorView(this);
alternatesPopup.setWidth(width);
+ alternatesPopup.setAnchorView(this);
alternatesPopup.setVerticalOffset(bottom);
alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
alternatesPopup.setOnItemClickListener(mAlternatesListener);
@@ -1112,14 +1147,18 @@
if (i == recipients.length - 1) {
totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
}
- recipients[i].storeChipStart(spannable.getSpanStart(recipients[i]));
- recipients[i].storeChipEnd(spannable.getSpanEnd(recipients[i]));
+ if (mTemporaryRecipients != null && !mTemporaryRecipients.contains(recipients[i])) {
+ recipients[i].storeChipStart(spannable.getSpanStart(recipients[i]));
+ recipients[i].storeChipEnd(spannable.getSpanEnd(recipients[i]));
+ }
spannable.removeSpan(recipients[i]);
}
- SpannableString chipText = new SpannableString(text.subSequence(totalReplaceStart,
- totalReplaceEnd));
+ // TODO: why would these ever be backwards?
+ int end = Math.max(totalReplaceStart, totalReplaceEnd);
+ int start = Math.min(totalReplaceStart, totalReplaceEnd);
+ SpannableString chipText = new SpannableString(text.subSequence(start, end));
chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- text.replace(totalReplaceStart, totalReplaceEnd, chipText);
+ text.replace(start, end, chipText);
mMoreChip = moreSpan;
}
@@ -1204,7 +1243,6 @@
}
newChip.setSelected(true);
showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
- setCursorVisible(false);
return newChip;
} else {
CharSequence text = currentChip.getValue();
@@ -1390,4 +1428,121 @@
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
}
-}
\ No newline at end of file
+
+ private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
+ private RecipientChip createFreeChip(RecipientEntry entry) {
+ String displayText = entry.getDestination();
+ displayText = (String) mTokenizer.terminateToken(displayText);
+ try {
+ return constructChipSpan(entry, -1, false);
+ } catch (NullPointerException e) {
+ Log.e(TAG, e.getMessage(), e);
+ return null;
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (mIndividualReplacements != null) {
+ mIndividualReplacements.cancel(true);
+ }
+ // For each chip in the list, look up the matching contact.
+ // If there is a match, replace that chip with the matching
+ // chip.
+ final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
+ RecipientChip[] existingChips = getSpannable().getSpans(0, getText().length(),
+ RecipientChip.class);
+ for (int i = 0; i < existingChips.length; i++) {
+ originalRecipients.add(existingChips[i]);
+ }
+ if (mRemovedSpans != null) {
+ originalRecipients.addAll(mRemovedSpans);
+ }
+ String[] addresses = new String[originalRecipients.size()];
+ for (int i = 0; i < originalRecipients.size(); i++) {
+ addresses[i] = originalRecipients.get(i).getEntry().getDestination();
+ }
+ HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
+ .getMatchingRecipients(getContext(), addresses);
+ final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
+ for (final RecipientChip temp : originalRecipients) {
+ RecipientEntry entry = null;
+ if (temp.getEntry().getContactId() == INVALID_CONTACT
+ && getSpannable().getSpanStart(temp) != -1) {
+ // Replace this.
+ entry = createValidatedEntry(entries.get(temp.getEntry().getDestination()));
+ }
+ if (entry != null) {
+ replacements.add(createFreeChip(entry));
+ } else {
+ replacements.add(temp);
+ }
+ }
+ if (replacements != null && replacements.size() > 0) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ SpannableStringBuilder text = new SpannableStringBuilder(getText()
+ .toString());
+ Editable oldText = getText();
+ SpannableString chipText;
+ int start, end;
+ int i = 0;
+ for (RecipientChip chip : originalRecipients) {
+ start = oldText.getSpanStart(chip);
+ if (start != -1) {
+ end = oldText.getSpanEnd(chip);
+ chipText = new SpannableString(text.subSequence(start, end));
+ chipText.setSpan(replacements.get(i), 0, end - start,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.removeSpan(chip);
+ text.replace(start, end, chipText);
+ }
+ i++;
+ }
+ Editable editable = getText();
+ editable.clear();
+ editable.insert(0, text);
+ originalRecipients.clear();
+ }
+ });
+ }
+ return null;
+ }
+ }
+
+ private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Void doInBackground(Object... params) {
+ // For each chip in the list, look up the matching contact.
+ // If there is a match, replace that chip with the matching
+ // chip.
+ final ArrayList<RecipientChip> originalRecipients =
+ (ArrayList<RecipientChip>) params[0];
+ String[] addresses = new String[originalRecipients.size()];
+ for (int i = 0; i < originalRecipients.size(); i++) {
+ addresses[i] = originalRecipients.get(i).getEntry().getDestination();
+ }
+ HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
+ .getMatchingRecipients(getContext(), addresses);
+ for (final RecipientChip temp : originalRecipients) {
+ if (temp.getEntry().getContactId() == INVALID_CONTACT
+ && getSpannable().getSpanStart(temp) != -1) {
+ // Replace this.
+ final RecipientEntry entry = createValidatedEntry(entries.get(temp.getEntry()
+ .getDestination()));
+ if (entry != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ replaceChip(temp, entry);
+ }
+ });
+ }
+ }
+ }
+ return null;
+ }
+ }
+}