From 19eb411905d411c029b0241ca690bb9313a02278 Mon Sep 17 00:00:00 2001 From: Mindy Pereira Date: Thu, 16 Jun 2011 18:54:10 -0700 Subject: Fix race condition. Crash was reported where the checked item position had yet to be set when the list tried to get it. This uses a listener to make sure that the checked item isnt set in the list until the adapter has it. Change-Id: I2b5170ab9440bbb0a69989657313b1a4b6d653cd --- .../ex/chips/RecipientAlternatesAdapter.java | 16 ++++++++----- .../android/ex/chips/RecipientEditTextView.java | 28 ++++++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java index 3d2c87b..792fdcc 100644 --- a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java +++ b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java @@ -37,7 +37,10 @@ public class RecipientAlternatesAdapter extends CursorAdapter { private int mCheckedItemPosition = -1; - public RecipientAlternatesAdapter(Context context, long contactId, long currentId, int viewId) { + private OnCheckedItemChangedListener mCheckedItemChangedListener; + + public RecipientAlternatesAdapter(Context context, long contactId, long currentId, int viewId, + OnCheckedItemChangedListener listener) { super(context, context.getContentResolver().query(Email.CONTENT_URI, EmailQuery.PROJECTION, Email.CONTACT_ID + " =?", new String[] { String.valueOf(contactId) @@ -45,6 +48,7 @@ public class RecipientAlternatesAdapter extends CursorAdapter { mLayoutInflater = LayoutInflater.from(context); mLayoutId = viewId; mCurrentId = currentId; + mCheckedItemChangedListener = listener; } @Override @@ -71,6 +75,9 @@ public class RecipientAlternatesAdapter extends CursorAdapter { } if (cursor.getLong(EmailQuery.DATA_ID) == mCurrentId) { mCheckedItemPosition = position; + if (mCheckedItemChangedListener != null) { + mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition); + } } bindView(convertView, convertView.getContext(), cursor); return convertView; @@ -109,10 +116,7 @@ public class RecipientAlternatesAdapter extends CursorAdapter { return mLayoutInflater.inflate(mLayoutId, null); } - /** - * Get the position of the item that should be checked. - */ - public int getCheckedItemPosition() { - return mCheckedItemPosition; + /*package*/ static interface OnCheckedItemChangedListener { + public void onCheckedItemChanged(int position); } } diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java index 53f7fad..5174869 100644 --- a/chips/src/com/android/ex/chips/RecipientEditTextView.java +++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java @@ -918,7 +918,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView * RecipientChip defines an ImageSpan that contains information relevant to * a particular recipient. */ - public class RecipientChip extends ImageSpan implements OnItemClickListener { + public class RecipientChip extends ImageSpan implements OnItemClickListener, + RecipientAlternatesAdapter.OnCheckedItemChangedListener { private final CharSequence mDisplay; private final CharSequence mValue; @@ -941,6 +942,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView private ListPopupWindow mAlternatesPopup; + private int mCheckedItem; + public RecipientChip(Drawable drawable, RecipientEntry entry, int offset) { super(drawable); mDisplay = entry.getDisplayName(); @@ -1156,8 +1159,9 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView int bottom = calculateLineBottom(xy[1], line, (int) mChipHeight); mAnchorView.setBottom(bottom); mAnchorView.setTop(bottom); - mAlternatesAdapter = new RecipientAlternatesAdapter(getContext(), - mEntry.getContactId(), mEntry.getDataId(), mAlternatesLayout); + mCheckedItem = -1; + mAlternatesAdapter = new RecipientAlternatesAdapter(getContext(), mEntry + .getContactId(), mEntry.getDataId(), mAlternatesLayout, this); // Align the alternates popup with the left side of the View, regardless // of the position of the chip tapped. mAlternatesPopup.setAnchorView(mAnchorView); @@ -1167,7 +1171,23 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView mAlternatesPopup.show(); ListView listView = mAlternatesPopup.getListView(); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - listView.setItemChecked(mAlternatesAdapter.getCheckedItemPosition(), true); + // Checked item would be -1 if the adapter has not + // loaded the view that should be checked yet. The + // variable will be set correctly when onCheckedItemChanged + // is called in a separate thread. + if (mCheckedItem != -1) { + listView.setItemChecked(mCheckedItem, true); + } + } + } + + @Override + public void onCheckedItemChanged(int position) { + ListView listView = mAlternatesPopup.getListView(); + if (listView != null && listView.getCheckedItemCount() == 0) { + listView.setItemChecked(position, true); + } else { + mCheckedItem = position; } } -- cgit v1.2.3 From dc5143e5d1eb107937e85654ef2fc79d8554752b Mon Sep 17 00:00:00 2001 From: Mindy Pereira Date: Tue, 21 Jun 2011 11:02:45 -0700 Subject: Remove space handling for now. This needs to be run only on NEWLY added recipients, notappended ones. Change-Id: I2754528ae660928d75b6a1e097976a151995944d --- .../src/com/android/ex/chips/RecipientEditTextView.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java index 66cdabd..dd2b3b2 100644 --- a/chips/src/com/android/ex/chips/RecipientEditTextView.java +++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java @@ -122,7 +122,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements private static final char COMMIT_CHAR_SEMICOLON = ';'; - private static final char COMMIT_CHAR_SPACE = ' '; + // TODO: do not run on appeneded recipients. + //private static final char COMMIT_CHAR_SPACE = ' '; private ListPopupWindow mAlternatesPopup; @@ -175,7 +176,10 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements char last = s.charAt(length() - 1); if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { commitDefault(); - } else if (last == COMMIT_CHAR_SPACE) { + } + // TODO: make sure this only runs AFTER appended recipients + // are handled. + /*else if (last == COMMIT_CHAR_SPACE) { // Check if this is a valid email address. If it is, commit it. String text = getText().toString(); int tokenStart = mTokenizer.findTokenStart(text, start); @@ -183,7 +187,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements if (mValidator != null && mValidator.isValid(sub)) { commitDefault(); } - } + }*/ } } @@ -652,10 +656,12 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements if (commitCharPosition != -1) { text = text.substring(0, commitCharPosition); } - commitCharPosition = text.indexOf(COMMIT_CHAR_SPACE); + // TODO: make sure this only runs AFTER appended recipients + // are handled. + /*commitCharPosition = text.indexOf(COMMIT_CHAR_SPACE); if (commitCharPosition != -1 && commitCharPosition != 0) { text = text.substring(0, commitCharPosition); - } + }*/ return text; } -- cgit v1.2.3 From fb91a5f9d871e06344dac005f9cc51a3fd4f9701 Mon Sep 17 00:00:00 2001 From: Mindy Pereira Date: Thu, 23 Jun 2011 17:11:06 -0700 Subject: Make sure we dont use textwatchers or commit chips incorrectly. 1) Never use textwatchers when there are any pending chips. These may be because there are recipients that were hidden when the more chip was shown, or because we are parsing appended recipients. 2) turn off item clicks for alternates before we send the dismiss event, since we dont dismiss immediately and we want to be sure the user cannot click 2 recipients if the device is running slowly. Change-Id: I53ae04a0f7c23f4ef7396ea203d8d1bb0a1b6e34 --- .../android/ex/chips/RecipientEditTextView.java | 41 +++++++++++++--------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java index 85a3f3e..a65ac57 100644 --- a/chips/src/com/android/ex/chips/RecipientEditTextView.java +++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java @@ -131,7 +131,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements */ private OnItemClickListener mAlternatesListener; - private int mCheckedItem; + private int mCheckedItem; private TextWatcher mTextWatcher; @@ -155,10 +155,11 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements @Override public void onItemClick(AdapterView adapterView,View view, int position, long rowId) { + mAlternatesPopup.setOnItemClickListener(null); replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) .getRecipientEntry(position)); Message delayed = Message.obtain(mHandler, DISMISS); - delayed.obj = RecipientEditTextView.this.mAlternatesPopup; + delayed.obj = mAlternatesPopup; mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); clearComposingText(); } @@ -627,7 +628,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements } private boolean shouldCreateChip(int start, int end) { - if (enoughToFilter()) { + if (hasFocus() && enoughToFilter()) { RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); if ((chips == null || chips.length == 0)) { return true; @@ -775,7 +776,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements alternatesPopup.setWidth(width); alternatesPopup.setAnchorView(anchorView); alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); - alternatesPopup.setOnItemClickListener(mAlternatesListener); // currentChip); + alternatesPopup.setOnItemClickListener(mAlternatesListener); alternatesPopup.show(); ListView listView = alternatesPopup.getListView(); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); @@ -983,26 +984,25 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements ImageSpan moreSpan = new ImageSpan(result); Spannable spannable = getSpannable(); // Remove the overage chips. - RecipientChip[] chips = spannable.getSpans(0, text.length(), RecipientChip.class); - if (chips == null || chips.length == 0) { + if (recipients == null || recipients.length == 0) { Log.w(TAG, "We have recipients. Tt should not be possible to have zero RecipientChips."); return null; } - mRemovedSpans = new ArrayList(chips.length); + mRemovedSpans = new ArrayList(); int totalReplaceStart = 0; int totalReplaceEnd = 0; - for (int i = numRecipients - overage; i < chips.length; i++) { - mRemovedSpans.add(chips[i]); + for (int i = numRecipients - overage; i < recipients.length; i++) { + mRemovedSpans.add(recipients[i]); if (i == numRecipients - overage) { - totalReplaceStart = spannable.getSpanStart(chips[i]); + totalReplaceStart = spannable.getSpanStart(recipients[i]); } - if (i == chips.length - 1) { - totalReplaceEnd = spannable.getSpanEnd(chips[i]); + if (i == recipients.length - 1) { + totalReplaceEnd = spannable.getSpanEnd(recipients[i]); } - chips[i].storeChipStart(spannable.getSpanStart(chips[i])); - chips[i].storeChipEnd(spannable.getSpanEnd(chips[i])); - spannable.removeSpan(chips[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)); @@ -1216,10 +1216,19 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements } } + /** + * Get whether there are any recipients pending addition to the view. + * If there are, don't do anything in the text watcher. + * @return + */ + private boolean chipsPending() { + return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); + } + private class RecipientTextWatcher implements TextWatcher { @Override public void afterTextChanged(Editable s) { - if (mPendingChipsCount > 0) { + if (chipsPending()) { return; } if (mSelectedChip != null) { -- cgit v1.2.3 From dd898627b7cd0d4dac98a8cbbd3bfffda0456a7a Mon Sep 17 00:00:00 2001 From: Mindy Pereira Date: Fri, 24 Jun 2011 11:26:20 -0700 Subject: When recipients are appended, take them as is without ANY parsing of tokens. Change-Id: I7ecc16cc032e7a6f4809ba1a323f7e445b8f74fd --- .../android/ex/chips/RecipientEditTextView.java | 40 ++++++++++------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java index a65ac57..e11d419 100644 --- a/chips/src/com/android/ex/chips/RecipientEditTextView.java +++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java @@ -100,6 +100,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements private ArrayList mRemovedSpans; + private ArrayList mPendingChips = new ArrayList(); + private float mChipHeight; private float mChipFontSize; @@ -223,6 +225,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) && TextUtils.getTrimmedLength(displayString) > 0) { mPendingChipsCount++; + mPendingChips.add((String)text); } } } @@ -234,6 +237,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements // Reset any pending chips as they would have been handled // when the field lost focus. mPendingChipsCount = 0; + mPendingChips.clear(); mHandler.post(mAddTextWatcher); } else { expand(); @@ -305,7 +309,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements * Get the background drawable for a RecipientChip. */ public Drawable getChipBackground(RecipientEntry contact) { - return mValidator != null && mValidator.isValid(contact.getDestination()) ? + return (mValidator != null && mValidator.isValid(contact.getDestination())) ? mChipBackground : mInvalidChipBackground; } @@ -461,30 +465,24 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements if (mPendingChipsCount > 0) { Editable editable = getText(); // Tokenize! - int startingPos = 0; - while (startingPos < editable.length() && mPendingChipsCount > 0) { - int tokenEnd = mTokenizer.findTokenEnd(editable, startingPos); - int tokenStart = mTokenizer.findTokenStart(editable, tokenEnd); - if (findChip(tokenStart) == null) { - // Always include seperators with the token to the - // left. - if (tokenEnd < editable.length() - 1 - && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { - tokenEnd++; - } - startingPos = tokenEnd; - String token = editable.toString().substring(tokenStart, tokenEnd); - int seperatorPos = token.indexOf(COMMIT_CHAR_COMMA); - if (seperatorPos != -1) { - token = token.substring(0, seperatorPos); - } - editable.replace(tokenStart, tokenEnd, createChip(RecipientEntry - .constructFakeEntry(token), false)); + for (int i = 0; i < mPendingChips.size(); i++) { + String current = mPendingChips.get(i); + int tokenStart = editable.toString().indexOf(current); + int tokenEnd = tokenStart + current.length(); + // Always include seperators with the token to the + // left. + if (tokenEnd < editable.length() - 1 + && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { + tokenEnd++; } - mPendingChipsCount--; + String token = editable.toString().substring(tokenStart, tokenEnd); + editable.replace(tokenStart, tokenEnd, createChip(RecipientEntry + .constructFakeEntry(token), false)); } + mPendingChipsCount--; } mPendingChipsCount = 0; + mPendingChips.clear(); mHandler.post(mAddTextWatcher); } } -- cgit v1.2.3