summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormindyp <mindyp@google.com>2012-12-03 12:16:33 -0800
committermindyp <mindyp@google.com>2012-12-03 12:24:25 -0800
commit44eb5f95c00a748e3a391f34ee27ddedd3ec9500 (patch)
treeea90629d53ad2c0e476e18552acce0246c67f7d4
parent82c598e17428a0c7049bc30c9a16eea4bb951d39 (diff)
downloadandroid_frameworks_ex-44eb5f95c00a748e3a391f34ee27ddedd3ec9500.tar.gz
android_frameworks_ex-44eb5f95c00a748e3a391f34ee27ddedd3ec9500.tar.bz2
android_frameworks_ex-44eb5f95c00a748e3a391f34ee27ddedd3ec9500.zip
Merge from ur8
Merges in: Fixup delete logic Its only a delete if the user has removed a single character, otherwise, we are looking at replacement, probably from a reverse lookup Ia7c7b94d9e2fb037cb19abdb6696f7df0d351856 Make sure we only use the invisible chips optimization if we are showing the "more" chip In the case of calendar, we never shrink the field, so this optimization can't be used I30e3312b8e686be5143f96d3d2820e023158957d mMaxLines was not being set correctly; dont draw hidden chips on first render pass mMaxLines was always zero Setting the value correctly avoids an extra layout which makes it faster to render chips Use an "invisible" recipient chip for anything > than max chips we can show before showing the more chip; this avoids a lot of unnecessary drawing and decoding of 9 patches in the first pass; once these are resolved (or the user makes them visible), they will be correctly drawn as visible chips Part of b/7492291 First 2 contacts takes too long to resolve into chips when there are many contacts I37817381f93efb968940031d8a8b77fb62c8919a Change-Id: I4cbe5eacaa6fc5d1d94a5663133da82621582824
-rw-r--r--chips/res/values/dimen.xml1
-rw-r--r--chips/src/com/android/ex/chips/BaseRecipientAdapter.java6
-rw-r--r--chips/src/com/android/ex/chips/InvisibleRecipientChip.java135
-rw-r--r--chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java6
-rw-r--r--chips/src/com/android/ex/chips/RecipientChip.java90
-rw-r--r--chips/src/com/android/ex/chips/RecipientEditTextView.java165
-rw-r--r--chips/src/com/android/ex/chips/RecipientEntry.java96
-rw-r--r--chips/src/com/android/ex/chips/VisibleRecipientChip.java124
8 files changed, 435 insertions, 188 deletions
diff --git a/chips/res/values/dimen.xml b/chips/res/values/dimen.xml
index b93555f..98354d2 100644
--- a/chips/res/values/dimen.xml
+++ b/chips/res/values/dimen.xml
@@ -19,4 +19,5 @@
<dimen name="chip_height">32dip</dimen>
<dimen name="chip_text_size">14sp</dimen>
<dimen name="line_spacing_extra">4dip</dimen>
+ <integer name="chips_max_lines">-1</integer>
</resources> \ No newline at end of file
diff --git a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
index c0cfa19..53f3625 100644
--- a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -646,7 +646,7 @@ public abstract class BaseRecipientAdapter extends BaseAdapter implements Filter
entry.displayName,
entry.displayNameSource,
entry.destination, entry.destinationType, entry.destinationLabel,
- entry.contactId, entry.dataId, entry.thumbnailUriString));
+ entry.contactId, entry.dataId, entry.thumbnailUriString, true));
} else if (entryMap.containsKey(entry.contactId)) {
// We already have a section for the person.
final List<RecipientEntry> entryList = entryMap.get(entry.contactId);
@@ -654,14 +654,14 @@ public abstract class BaseRecipientAdapter extends BaseAdapter implements Filter
entry.displayName,
entry.displayNameSource,
entry.destination, entry.destinationType, entry.destinationLabel,
- entry.contactId, entry.dataId, entry.thumbnailUriString));
+ entry.contactId, entry.dataId, entry.thumbnailUriString, true));
} else {
final List<RecipientEntry> entryList = new ArrayList<RecipientEntry>();
entryList.add(RecipientEntry.constructTopLevelEntry(
entry.displayName,
entry.displayNameSource,
entry.destination, entry.destinationType, entry.destinationLabel,
- entry.contactId, entry.dataId, entry.thumbnailUriString));
+ entry.contactId, entry.dataId, entry.thumbnailUriString, true));
entryMap.put(entry.contactId, entryList);
}
}
diff --git a/chips/src/com/android/ex/chips/InvisibleRecipientChip.java b/chips/src/com/android/ex/chips/InvisibleRecipientChip.java
new file mode 100644
index 0000000..46fe19a
--- /dev/null
+++ b/chips/src/com/android/ex/chips/InvisibleRecipientChip.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2011 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.ex.chips;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Rect;
+import android.text.TextUtils;
+import android.text.style.ReplacementSpan;
+
+/**
+ * RecipientChip defines a span that contains information relevant to a
+ * particular recipient.
+ */
+/* package */class InvisibleRecipientChip extends ReplacementSpan implements RecipientChip {
+ private final CharSequence mDisplay;
+
+ private final CharSequence mValue;
+
+ private final long mContactId;
+
+ private final long mDataId;
+
+ private RecipientEntry mEntry;
+
+ private boolean mSelected = false;
+
+ private CharSequence mOriginalText;
+
+ public InvisibleRecipientChip(RecipientEntry entry, int offset) {
+ super();
+ mDisplay = entry.getDisplayName();
+ mValue = entry.getDestination().trim();
+ mContactId = entry.getContactId();
+ mDataId = entry.getDataId();
+ mEntry = entry;
+ }
+
+ /**
+ * Set the selected state of the chip.
+ * @param selected
+ */
+ public void setSelected(boolean selected) {
+ mSelected = selected;
+ }
+
+ /**
+ * Return true if the chip is selected.
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ /**
+ * Get the text displayed in the chip.
+ */
+ public CharSequence getDisplay() {
+ return mDisplay;
+ }
+
+ /**
+ * Get the text value this chip represents.
+ */
+ public CharSequence getValue() {
+ return mValue;
+ }
+
+ /**
+ * Get the id of the contact associated with this chip.
+ */
+ public long getContactId() {
+ return mContactId;
+ }
+
+ /**
+ * Get the id of the data associated with this chip.
+ */
+ public long getDataId() {
+ return mDataId;
+ }
+
+ /**
+ * Get associated RecipientEntry.
+ */
+ public RecipientEntry getEntry() {
+ return mEntry;
+ }
+
+ public void setOriginalText(String text) {
+ if (!TextUtils.isEmpty(text)) {
+ text = text.trim();
+ }
+ mOriginalText = text;
+ }
+
+ public CharSequence getOriginalText() {
+ return !TextUtils.isEmpty(mOriginalText) ? mOriginalText : mEntry.getDestination();
+ }
+
+ @Override
+ public void draw(Canvas arg0, CharSequence arg1, int arg2, int arg3, float arg4, int arg5,
+ int arg6, int arg7, Paint arg8) {
+ // Do nothing.
+ }
+
+ @Override
+ public int getSize(Paint arg0, CharSequence arg1, int arg2, int arg3, FontMetricsInt arg4) {
+ return 0;
+ }
+
+ @Override
+ public Rect getBounds() {
+ return new Rect(0, 0, 0, 0);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // do nothing.
+ }
+}
diff --git a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index 553890e..b54d83d 100644
--- a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -114,7 +114,8 @@ public class RecipientAlternatesAdapter extends CursorAdapter {
c.getString(Queries.Query.DESTINATION_LABEL),
c.getLong(Queries.Query.CONTACT_ID),
c.getLong(Queries.Query.DATA_ID),
- c.getString(Queries.Query.PHOTO_THUMBNAIL_URI)));
+ c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
+ true));
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Received reverse look up information for " + address
+ " RESULTS: "
@@ -233,7 +234,8 @@ public class RecipientAlternatesAdapter extends CursorAdapter {
c.getString(Queries.Query.DESTINATION_LABEL),
c.getLong(Queries.Query.CONTACT_ID),
c.getLong(Queries.Query.DATA_ID),
- c.getString(Queries.Query.PHOTO_THUMBNAIL_URI));
+ c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
+ true);
}
@Override
diff --git a/chips/src/com/android/ex/chips/RecipientChip.java b/chips/src/com/android/ex/chips/RecipientChip.java
index 0f93a0d..c7745c2 100644
--- a/chips/src/com/android/ex/chips/RecipientChip.java
+++ b/chips/src/com/android/ex/chips/RecipientChip.java
@@ -16,97 +16,69 @@
package com.android.ex.chips;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.text.style.DynamicDrawableSpan;
-import android.text.style.ImageSpan;
+import android.graphics.Canvas;
+import android.graphics.Rect;
/**
- * RecipientChip defines an ImageSpan that contains information relevant to a
+ * RecipientChip defines a drawable object that contains information relevant to a
* particular recipient.
*/
-/* package */class RecipientChip extends ImageSpan {
- private final CharSequence mDisplay;
-
- private final CharSequence mValue;
-
- private final long mContactId;
-
- private final long mDataId;
-
- private RecipientEntry mEntry;
-
- private boolean mSelected = false;
-
- private CharSequence mOriginalText;
-
- public RecipientChip(Drawable drawable, RecipientEntry entry, int offset) {
- super(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
- mDisplay = entry.getDisplayName();
- mValue = entry.getDestination().trim();
- mContactId = entry.getContactId();
- mDataId = entry.getDataId();
- mEntry = entry;
- }
+/* package */interface RecipientChip {
/**
* Set the selected state of the chip.
* @param selected
*/
- public void setSelected(boolean selected) {
- mSelected = selected;
- }
-
+ public void setSelected(boolean selected);
/**
* Return true if the chip is selected.
*/
- public boolean isSelected() {
- return mSelected;
- }
+ public boolean isSelected();
/**
* Get the text displayed in the chip.
*/
- public CharSequence getDisplay() {
- return mDisplay;
- }
+ public CharSequence getDisplay();
/**
* Get the text value this chip represents.
*/
- public CharSequence getValue() {
- return mValue;
- }
+ public CharSequence getValue();
/**
* Get the id of the contact associated with this chip.
*/
- public long getContactId() {
- return mContactId;
- }
+ public long getContactId();
/**
* Get the id of the data associated with this chip.
*/
- public long getDataId() {
- return mDataId;
- }
+ public long getDataId();
/**
* Get associated RecipientEntry.
*/
- public RecipientEntry getEntry() {
- return mEntry;
- }
+ public RecipientEntry getEntry();
+
+ /**
+ * Set the text in the edittextview originally associated with this chip
+ * before any reverse lookups.
+ */
+ public void setOriginalText(String text);
- public void setOriginalText(String text) {
- if (!TextUtils.isEmpty(text)) {
- text = text.trim();
- }
- mOriginalText = text;
- }
+ /**
+ * Set the text in the edittextview originally associated with this chip
+ * before any reverse lookups.
+ */
+ public CharSequence getOriginalText();
- public CharSequence getOriginalText() {
- return !TextUtils.isEmpty(mOriginalText) ? mOriginalText : mEntry.getDestination();
- }
+ /**
+ * Get the bounds of the chip; may be 0,0 if it is not visibly rendered.
+ */
+ public Rect getBounds();
+
+ /**
+ * Draw the chip.
+ */
+ public void draw(Canvas canvas);
}
diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java
index 564bfe1..29787ab 100644
--- a/chips/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java
@@ -43,7 +43,6 @@ import android.text.InputType;
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;
@@ -203,7 +202,6 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
+ "(1?[ ]*\\([0-9]+\\)[\\- \\.]*)?" // 1(<digits>)<sdd>*
+ "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
-
private final Runnable mAddTextWatcher = new Runnable() {
@Override
public void run() {
@@ -275,7 +273,6 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
addTextChangedListener(mTextWatcher);
mGestureDetector = new GestureDetector(context, this);
setOnEditorActionListener(this);
- mMaxLines = getLineCount();
}
@Override
@@ -601,9 +598,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
* Get the background drawable for a RecipientChip.
*/
// Visible for testing.
- /*package*/ Drawable getChipBackground(RecipientEntry contact) {
- return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
- mChipBackground : mInvalidChipBackground;
+ /* package */Drawable getChipBackground(RecipientEntry contact) {
+ return contact.isValid() ? mChipBackground : mInvalidChipBackground;
}
private float getTextYOffset(String text, TextPaint paint, int height) {
@@ -636,7 +632,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
// Pass the full text, un-ellipsized, to the chip.
Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
- RecipientChip recipientChip = new RecipientChip(result, contact, offset);
+ RecipientChip recipientChip = new VisibleRecipientChip(result, contact, offset);
// Return text to the original size.
paint.setTextSize(defaultSize);
paint.setColor(defaultColor);
@@ -710,7 +706,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
if (mInvalidChipBackground == null) {
mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
}
- mLineSpacingExtra = context.getResources().getDimension(R.dimen.line_spacing_extra);
+ mLineSpacingExtra = r.getDimension(R.dimen.line_spacing_extra);
+ mMaxLines = r.getInteger(R.integer.chips_max_lines);
a.recycle();
}
@@ -775,7 +772,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
if (chips != null) {
Rect bounds;
for (RecipientChip chip : chips) {
- bounds = chip.getDrawable().getBounds();
+ bounds = chip.getBounds();
if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
// Need to redraw that chip.
replaceChip(chip, chip.getEntry());
@@ -811,7 +808,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
&& editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
tokenEnd++;
}
- createReplacementChip(tokenStart, tokenEnd, editable);
+ createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT
+ || !mShouldShrink);
}
mPendingChipsCount--;
}
@@ -866,16 +864,15 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
}
// Find the last chip; eliminate any commit characters after it.
RecipientChip[] chips = getSortedRecipients();
+ Spannable spannable = getSpannable();
if (chips != null && chips.length > 0) {
int end;
- ImageSpan lastSpan;
mMoreChip = getMoreChip();
if (mMoreChip != null) {
- lastSpan = mMoreChip;
+ end = spannable.getSpanEnd(mMoreChip);
} else {
- lastSpan = getLastChip();
+ end = getSpannable().getSpanEnd(getLastChip());
}
- end = getSpannable().getSpanEnd(lastSpan);
Editable editable = getText();
int length = editable.length();
if (length > end) {
@@ -893,7 +890,8 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
* 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) {
+ private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable,
+ boolean visible) {
if (alreadyHasChip(tokenStart, tokenEnd)) {
// There is already a chip present at this location.
// Don't recreate it.
@@ -908,19 +906,19 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
if (entry != null) {
String destText = createAddressText(entry);
SpannableString chipText = new SpannableString(destText);
- int end = getSelectionEnd();
- int start = mTokenizer != null ? mTokenizer.findTokenStart(getText(), end) : 0;
RecipientChip chip = null;
try {
if (!mNoChips) {
- /* leave space for the contact icon if this is not just an email address */
- chip = constructChipSpan(
- entry,
- start,
- false,
- TextUtils.isEmpty(entry.getDisplayName())
- || TextUtils.equals(entry.getDisplayName(),
- entry.getDestination()));
+ /*
+ * leave space for the contact icon if this is not just an
+ * email address
+ */
+ boolean leaveSpace = TextUtils.isEmpty(entry.getDisplayName())
+ || TextUtils.equals(entry.getDisplayName(),
+ entry.getDestination());
+ chip = visible ?
+ constructChipSpan(entry, tokenStart, false, leaveSpace)
+ : new InvisibleRecipientChip(entry, commitCharIndex);
}
} catch (NullPointerException e) {
Log.e(TAG, e.getMessage(), e);
@@ -954,58 +952,55 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
return null;
}
if (isPhoneQuery() && isPhoneNumber(token)) {
- return RecipientEntry
- .constructFakeEntry(token);
+ return RecipientEntry.constructFakeEntry(token, true);
}
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
String display = null;
- if (isValid(token) && tokens != null && tokens.length > 0) {
+ boolean isValid = isValid(token);
+ if (isValid && tokens != null && tokens.length > 0) {
// If we can get a name from tokenizing, then generate an entry from
// this.
display = tokens[0].getName();
if (!TextUtils.isEmpty(display)) {
- if (!isPhoneQuery()) {
- if (!TextUtils.isEmpty(token)) {
- token = token.trim();
- }
- char charAt = token.charAt(token.length() - 1);
- if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
- token = token.substring(0, token.length() - 1);
- }
- }
- return RecipientEntry.constructGeneratedEntry(display, token);
+ return RecipientEntry.constructGeneratedEntry(display, tokens[0].getAddress(),
+ isValid);
} else {
display = tokens[0].getAddress();
if (!TextUtils.isEmpty(display)) {
- return RecipientEntry.constructFakeEntry(display);
+ return RecipientEntry.constructFakeEntry(display, isValid);
}
}
}
// Unable to validate the token or to create a valid token from it.
// Just create a chip the user can edit.
String validatedToken = null;
- if (mValidator != null && !mValidator.isValid(token)) {
+ if (mValidator != null && !isValid) {
// Try fixing up the entry using the validator.
validatedToken = mValidator.fixText(token).toString();
if (!TextUtils.isEmpty(validatedToken)) {
if (validatedToken.contains(token)) {
- // protect against the case of a validator with a null domain,
+ // protect against the case of a validator with a null
+ // domain,
// which doesn't add a domain to the token
Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
if (tokenized.length > 0) {
validatedToken = tokenized[0].getAddress();
+ isValid = true;
}
} else {
- // We ran into a case where the token was invalid and removed
- // by the validator. In this case, just use the original token
+ // We ran into a case where the token was invalid and
+ // removed
+ // by the validator. In this case, just use the original
+ // token
// and let the user sort out the error chip.
validatedToken = null;
+ isValid = false;
}
}
}
// Otherwise, fallback to just creating an editable email address chip.
- return RecipientEntry
- .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
+ return RecipientEntry.constructFakeEntry(
+ !TextUtils.isEmpty(validatedToken) ? validatedToken : token, isValid);
}
private boolean isValid(String text) {
@@ -1249,7 +1244,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
setSelection(end);
String text = getText().toString().substring(start, end);
if (!TextUtils.isEmpty(text)) {
- RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
+ RecipientEntry entry = RecipientEntry.constructFakeEntry(text, isValid(text));
QwertyKeyListener.markAsReplaced(editable, start, end, "");
CharSequence chipText = createChip(entry, false);
int selEnd = getSelectionEnd();
@@ -1535,14 +1530,6 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
display = null;
}
- if (address != null && !(isPhoneQuery() && isPhoneNumber(address))) {
- // Tokenize out the address in case the address already
- // contained the username as well.
- Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
- if (tokenized != null && tokenized.length > 0) {
- address = tokenized[0].getAddress();
- }
- }
if (!TextUtils.isEmpty(display)) {
return display;
} else if (!TextUtils.isEmpty(address)){
@@ -1618,12 +1605,12 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
String destination = item.getDestination();
if (!isPhoneQuery() && item.getContactId() == RecipientEntry.GENERATED_CONTACT) {
entry = RecipientEntry.constructGeneratedEntry(item.getDisplayName(),
- destination);
+ destination, item.isValid());
} else if (RecipientEntry.isCreatedRecipient(item.getContactId())
&& (TextUtils.isEmpty(item.getDisplayName())
|| TextUtils.equals(item.getDisplayName(), destination)
|| (mValidator != null && !mValidator.isValid(destination)))) {
- entry = RecipientEntry.constructFakeEntry(destination);
+ entry = RecipientEntry.constructFakeEntry(destination, item.isValid());
} else {
entry = item;
}
@@ -1888,10 +1875,17 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
if (shouldShowEditableText(currentChip)) {
CharSequence text = currentChip.getValue();
Editable editable = getText();
- getSpannable().removeSpan(currentChip);
+ Spannable spannable = getSpannable();
+ int spanStart = spannable.getSpanStart(currentChip);
+ int spanEnd = spannable.getSpanEnd(currentChip);
+ spannable.removeSpan(currentChip);
+ editable.delete(spanStart, spanEnd);
setCursorVisible(true);
setSelection(editable.length());
- return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
+ editable.append(text);
+ return constructChipSpan(
+ RecipientEntry.constructFakeEntry((String) text, isValid(text.toString())),
+ getSelectionStart(), true, false);
} else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
int start = getChipStart(currentChip);
int end = getChipEnd(currentChip);
@@ -2142,16 +2136,23 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
return;
}
// If the user is editing a chip, don't clear it.
- if (mSelectedChip != null
- && shouldShowEditableText(mSelectedChip)) {
- setCursorVisible(true);
- setSelection(getText().length());
- clearSelectedChip();
+ if (mSelectedChip != null) {
+ if (!isGeneratedContact(mSelectedChip)) {
+ setCursorVisible(true);
+ setSelection(getText().length());
+ clearSelectedChip();
+ } else {
+ return;
+ }
}
int length = s.length();
// Make sure there is content there to parse and that it is
// not just the commit character.
if (length > 1) {
+ if (lastCharacterIsCommitCharacter(s)) {
+ commitByCharacter();
+ return;
+ }
char last;
int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
int len = length() - 1;
@@ -2160,9 +2161,7 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
} else {
last = s.charAt(len);
}
- if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
- commitByCharacter();
- } else if (last == COMMIT_CHAR_SPACE) {
+ if (last == COMMIT_CHAR_SPACE) {
if (!isPhoneQuery()) {
// Check if this is a valid email address. If it is,
// commit it.
@@ -2181,9 +2180,10 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
- // This is a delete; check to see if the insertion point is on a space
+ // The user deleted some text OR some text was replaced; check to
+ // see if the insertion point is on a space
// following a chip.
- if (before > count) {
+ if (before - count == 1) {
// If the item deleted is a space, and the thing before the
// space is a chip, delete the entire span.
int selStart = getSelectionStart();
@@ -2203,6 +2203,13 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
getSpannable().removeSpan(repl[0]);
}
} else if (count > before) {
+ if (mSelectedChip != null
+ && isGeneratedContact(mSelectedChip)) {
+ if (lastCharacterIsCommitCharacter(s)) {
+ commitByCharacter();
+ return;
+ }
+ }
scrollBottomIntoView();
}
}
@@ -2219,6 +2226,24 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
}
}
+ public boolean lastCharacterIsCommitCharacter(CharSequence s) {
+ char last;
+ int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
+ int len = length() - 1;
+ if (end != len) {
+ last = s.charAt(end);
+ } else {
+ last = s.charAt(len);
+ }
+ return last == COMMIT_CHAR_COMMA || last == COMMIT_CHAR_SEMICOLON;
+ }
+
+ public boolean isGeneratedContact(RecipientChip chip) {
+ long contactId = chip.getContactId();
+ return contactId == RecipientEntry.INVALID_CONTACT
+ || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT);
+ }
+
/**
* Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
*/
@@ -2559,14 +2584,14 @@ public class RecipientEditTextView extends MultiAutoCompleteTextView implements
@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
- Rect rect = mChip.getDrawable().getBounds();
+ Rect rect = mChip.getBounds();
shadowSize.set(rect.width(), rect.height());
shadowTouchPoint.set(rect.centerX(), rect.centerY());
}
@Override
public void onDrawShadow(Canvas canvas) {
- mChip.getDrawable().draw(canvas);
+ mChip.draw(canvas);
}
}
diff --git a/chips/src/com/android/ex/chips/RecipientEntry.java b/chips/src/com/android/ex/chips/RecipientEntry.java
index 0448229..98a35e6 100644
--- a/chips/src/com/android/ex/chips/RecipientEntry.java
+++ b/chips/src/com/android/ex/chips/RecipientEntry.java
@@ -65,29 +65,16 @@ public class RecipientEntry {
private final Uri mPhotoThumbnailUri;
+ private boolean mIsValid;
/**
* This can be updated after this object being constructed, when the photo is fetched
* from remote directories.
*/
private byte[] mPhotoBytes;
- private RecipientEntry(int entryType) {
- mEntryType = entryType;
- mDisplayName = null;
- mDestination = null;
- mDestinationType = INVALID_DESTINATION_TYPE;
- mDestinationLabel = null;
- mContactId = -1;
- mDataId = -1;
- mPhotoThumbnailUri = null;
- mPhotoBytes = null;
- mIsDivider = true;
- }
-
- private RecipientEntry(
- int entryType, String displayName,
- String destination, int destinationType, String destinationLabel,
- long contactId, long dataId, Uri photoThumbnailUri, boolean isFirstLevel) {
+ private RecipientEntry(int entryType, String displayName, String destination,
+ int destinationType, String destinationLabel, long contactId, long dataId,
+ Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid) {
mEntryType = entryType;
mIsFirstLevel = isFirstLevel;
mDisplayName = displayName;
@@ -99,6 +86,11 @@ public class RecipientEntry {
mPhotoThumbnailUri = photoThumbnailUri;
mPhotoBytes = null;
mIsDivider = false;
+ mIsValid = isValid;
+ }
+
+ public boolean isValid() {
+ return mIsValid;
}
/**
@@ -114,10 +106,10 @@ public class RecipientEntry {
* This address has not been resolved to a contact and therefore does not
* have a contact id or photo.
*/
- public static RecipientEntry constructFakeEntry(String address) {
+ public static RecipientEntry constructFakeEntry(String address, boolean isValid) {
return new RecipientEntry(ENTRY_TYPE_PERSON, address, address,
INVALID_DESTINATION_TYPE, null,
- INVALID_CONTACT, INVALID_CONTACT, null, true);
+ INVALID_CONTACT, INVALID_CONTACT, null, true, isValid);
}
/**
@@ -136,41 +128,37 @@ public class RecipientEntry {
* with both an associated display name. This address has not been resolved
* to a contact and therefore does not have a contact id or photo.
*/
- public static RecipientEntry constructGeneratedEntry(String display, String address) {
- return new RecipientEntry(ENTRY_TYPE_PERSON, display,
- address, INVALID_DESTINATION_TYPE, null,
- GENERATED_CONTACT, GENERATED_CONTACT, null, true);
- }
-
- public static RecipientEntry constructTopLevelEntry(
- String displayName, int displayNameSource, String destination, int destinationType,
- String destinationLabel, long contactId, long dataId, Uri photoThumbnailUri) {
- return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, displayName,
- destination),
- destination, destinationType, destinationLabel,
- contactId, dataId,
- photoThumbnailUri, true);
- }
-
- public static RecipientEntry constructTopLevelEntry(
- String displayName, int displayNameSource, String destination, int destinationType,
- String destinationLabel, long contactId, long dataId,
- String thumbnailUriAsString) {
- return new RecipientEntry(
- ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, displayName, destination),
- destination, destinationType, destinationLabel,
- contactId, dataId,
- (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString) : null), true);
- }
-
- public static RecipientEntry constructSecondLevelEntry(
- String displayName, int displayNameSource, String destination, int destinationType,
- String destinationLabel, long contactId, long dataId, String thumbnailUriAsString) {
- return new RecipientEntry(
- ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, displayName, destination),
- destination, destinationType, destinationLabel,
- contactId, dataId,
- (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString) : null), false);
+ public static RecipientEntry constructGeneratedEntry(String display, String address,
+ boolean isValid) {
+ return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
+ null, GENERATED_CONTACT, GENERATED_CONTACT, null, true, isValid);
+ }
+
+ public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
+ String destination, int destinationType, String destinationLabel, long contactId,
+ long dataId, Uri photoThumbnailUri, boolean isValid) {
+ return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+ displayName, destination), destination, destinationType, destinationLabel,
+ contactId, dataId, photoThumbnailUri, true, isValid);
+ }
+
+ public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
+ String destination, int destinationType, String destinationLabel, long contactId,
+ long dataId, String thumbnailUriAsString, boolean isValid) {
+ return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+ displayName, destination), destination, destinationType, destinationLabel,
+ contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
+ : null), true, isValid);
+ }
+
+ public static RecipientEntry constructSecondLevelEntry(String displayName,
+ int displayNameSource, String destination, int destinationType,
+ String destinationLabel, long contactId, long dataId, String thumbnailUriAsString,
+ boolean isValid) {
+ return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+ displayName, destination), destination, destinationType, destinationLabel,
+ contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
+ : null), false, isValid);
}
public int getEntryType() {
diff --git a/chips/src/com/android/ex/chips/VisibleRecipientChip.java b/chips/src/com/android/ex/chips/VisibleRecipientChip.java
new file mode 100644
index 0000000..257e3f4
--- /dev/null
+++ b/chips/src/com/android/ex/chips/VisibleRecipientChip.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2011 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.ex.chips;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
+
+/**
+ * VisibleRecipientChip defines an ImageSpan that contains information relevant to a
+ * particular recipient and renders a background asset to go with it.
+ */
+/* package */class VisibleRecipientChip extends ImageSpan implements RecipientChip {
+ private final CharSequence mDisplay;
+
+ private final CharSequence mValue;
+
+ private final long mContactId;
+
+ private final long mDataId;
+
+ private RecipientEntry mEntry;
+
+ private boolean mSelected = false;
+
+ private CharSequence mOriginalText;
+
+ public VisibleRecipientChip(Drawable drawable, RecipientEntry entry, int offset) {
+ super(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
+ mDisplay = entry.getDisplayName();
+ mValue = entry.getDestination().trim();
+ mContactId = entry.getContactId();
+ mDataId = entry.getDataId();
+ mEntry = entry;
+ }
+
+ /**
+ * Set the selected state of the chip.
+ * @param selected
+ */
+ public void setSelected(boolean selected) {
+ mSelected = selected;
+ }
+
+ /**
+ * Return true if the chip is selected.
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ /**
+ * Get the text displayed in the chip.
+ */
+ public CharSequence getDisplay() {
+ return mDisplay;
+ }
+
+ /**
+ * Get the text value this chip represents.
+ */
+ public CharSequence getValue() {
+ return mValue;
+ }
+
+ /**
+ * Get the id of the contact associated with this chip.
+ */
+ public long getContactId() {
+ return mContactId;
+ }
+
+ /**
+ * Get the id of the data associated with this chip.
+ */
+ public long getDataId() {
+ return mDataId;
+ }
+
+ /**
+ * Get associated RecipientEntry.
+ */
+ public RecipientEntry getEntry() {
+ return mEntry;
+ }
+
+ public void setOriginalText(String text) {
+ if (!TextUtils.isEmpty(text)) {
+ text = text.trim();
+ }
+ mOriginalText = text;
+ }
+
+ public CharSequence getOriginalText() {
+ return !TextUtils.isEmpty(mOriginalText) ? mOriginalText : mEntry.getDestination();
+ }
+
+ @Override
+ public Rect getBounds() {
+ return getDrawable().getBounds();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ getDrawable().draw(canvas);
+ }
+}