RecipientEditTextView.java revision f566dee91901e44db63df3bf393afb1d43a36f78
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/* 22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project 32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License. 62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at 72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software 112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and 142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License. 152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips; 182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context; 202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Bitmap; 212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Canvas; 222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Paint; 232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Rect; 242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.BitmapDrawable; 252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.os.Handler; 272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Layout; 292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Selection; 302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Spannable; 312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.SpannableString; 322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Spanned; 332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.TextPaint; 342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.TextUtils; 352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.method.QwertyKeyListener; 362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.style.ImageSpan; 372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 38078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereiraimport android.util.Log; 392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.view.KeyEvent; 402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.view.MotionEvent; 412d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.view.View; 422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.AdapterView; 432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 442d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.PopupWindow.OnDismissListener; 452d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.ListPopupWindow; 462d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 492d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 512d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView 532d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira implements OnItemClickListener { 542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 55078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira private static final String TAG = "RecipientEditTextView"; 562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 57078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira private Drawable mChipBackground = null; 582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 59078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira private int mChipPadding; 602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private Tokenizer mTokenizer; 622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private final Handler mHandler; 642d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 652d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private Runnable mDelayedSelectionMode = new Runnable() { 662d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 672d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void run() { 682d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira setSelection(getText().length()); 692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira }; 712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mHandler = new Handler(); 752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira setOnItemClickListener(this); 762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 78f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 79078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira throws NullPointerException { 80078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira if (mChipBackground == null) { 81078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira throw new NullPointerException 82078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira ("Unable to render any chips as setChipDimensions was not called."); 83078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 84f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira String text = contact.getDisplayName(); 852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Layout layout = getLayout(); 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int line = layout.getLineForOffset(offset); 872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int lineTop = layout.getLineTop(line); 882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira TextPaint paint = getPaint(); 902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira float defaultSize = paint.getTextSize(); 912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Reduce the size of the text slightly so that we can get the "look" of 932d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // padding. 942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira paint.setTextSize((float) (paint.getTextSize() * .9)); 952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // on the sides. 992d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira CharSequence ellipsizedText = TextUtils.ellipsize(text, paint, calculateAvailableWidth(), 1002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira TextUtils.TruncateAt.END); 1012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1022d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int height = getLineHeight(); 1032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int width = (int) Math.floor(paint.measureText(ellipsizedText, 0, ellipsizedText.length())) 104078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira + (mChipPadding * 2); 1052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Create the background of the chip. 1072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 1082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 1092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira if (mChipBackground != null) { 1102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mChipBackground.setBounds(0, 0, width, height); 1112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mChipBackground.draw(canvas); 1122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } else { 113078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira Log.w(TAG, 114078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira "Unable to draw a background for the chips as it was never set"); 1152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 1162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Align the display text with where the user enters text. 118078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height 1192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira - layout.getLineDescent(line), paint); 1202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Get the location of the widget so we can properly offset 1222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // the anchor for each chip. 1232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int[] xy = new int[2]; 1242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira getLocationOnScreen(xy); 1252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Pass the full text, un-ellipsized, to the chip. 1262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 1272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira result.setBounds(0, 0, width, height); 128f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + width, 129f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira calculateLineBottom(xy[1], line)); 130f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds); 1312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Return text to the original size. 1332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira paint.setTextSize(defaultSize); 1342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return recipientChip; 1362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 1372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 138f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // The bottom of the line the chip will be located on is calculated by 4 factors: 139f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // 1) which line the chip appears on 140f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // 2) the height of a line in the autocomplete view 141f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // 3) padding built into the edit text view will move the bottom position 142f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // 4) the position of the autocomplete view on the screen, taking into account 143f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // that any top padding will move this down visually 144f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira private int calculateLineBottom(int yOffset, int line) { 145f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira int bottomPadding = 0; 146f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira if (line == getLineCount() - 1) { 147f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira bottomPadding += getPaddingBottom(); 148f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 149f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira return ((line + 1) * getLineHeight()) + (yOffset + getPaddingTop()) + bottomPadding; 150f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 151f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 1522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Get the max amount of space a chip can take up. The formula takes into 1532d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // account the width of the EditTextView, any view padding, and padding 1542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // that will be added to the chip. 1552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private float calculateAvailableWidth() { 156078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 1572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 1582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 159078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira /** 160078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira * Set all chip dimensions and resources. This has to be done from the application 161078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira * as this is a static library. 162078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira * @param chipBackground drawable 163078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira * @param padding Padding around the text in a chip 164078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira * @param offset Offset between the chip and the dropdown of alternates 165078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira */ 166f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira public void setChipDimensions(Drawable chipBackground, float padding) { 167078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira mChipBackground = chipBackground; 168078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira mChipPadding = (int) padding; 1692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 1702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 1722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void setTokenizer(Tokenizer tokenizer) { 1732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mTokenizer = tokenizer; 1742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super.setTokenizer(mTokenizer); 1752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 1762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // We want to handle replacing text in the onItemClickListener 1782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // so we can get all the associated contact information including 1792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // display text, address, and id. 1802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 1812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira protected void replaceText(CharSequence text) { 1822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return; 1832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 1842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 1862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 1872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira switch (keyCode) { 1882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira case KeyEvent.KEYCODE_ENTER: 1892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 1902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira case KeyEvent.KEYCODE_TAB: 1912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira if (event.hasNoModifiers()) { 192078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira if (isPopupShowing()) { 193078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira // choose the first entry. 194078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira submitItemAtPosition(0); 195078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira dismissDropDown(); 1962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return true; 1972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } else { 1982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int end = getSelectionEnd(); 1992d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 2002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira String text = getText().toString().substring(start, end); 2012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira clearComposingText(); 2022d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 2032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Editable editable = getText(); 204f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 2052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 206f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira editable.replace(start, end, createChip(entry)); 2072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira dismissDropDown(); 2082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return super.onKeyUp(keyCode, event); 2122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 2142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void onChipChanged() { 2152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // Must be posted so that the previous span 2162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // is correctly replaced with the previous selection points. 2172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mHandler.post(mDelayedSelectionMode); 2182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 2202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 2212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 2222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int start = getSelectionStart(); 2232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int end = getSelectionEnd(); 2242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Spannable span = getSpannable(); 2252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 2262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 2272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira if (chips != null) { 2282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira for (RecipientChip chip : chips) { 2292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira chip.onKeyDown(keyCode, event); 2302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 2332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 2342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return true; 2352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 2372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return super.onKeyDown(keyCode, event); 2382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 240078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira private Spannable getSpannable() { 2412d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return (Spannable) getText(); 2422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 244078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 245078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira @Override 246078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira public boolean onTouchEvent(MotionEvent event) { 247078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int action = event.getAction(); 248078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { 249078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira Spannable span = getSpannable(); 250078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int offset = getOffsetForPosition(event.getX(), event.getY()); 251078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int start = offset; 252078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int end = span.length(); 253078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 254078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira if (chips != null && chips.length > 0) { 255078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira // Get the first chip that matched. 256078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira final RecipientChip currentChip = chips[0]; 257078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 258078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira if (action == MotionEvent.ACTION_UP) { 259078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira currentChip.onClick(this); 260078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } else if (action == MotionEvent.ACTION_DOWN) { 261078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira Selection.setSelection(getSpannable(), currentChip.getChipStart(), currentChip 262078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira .getChipEnd()); 263078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 264078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira return true; 265078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 266078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 267078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 268078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira return super.onTouchEvent(event); 269078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 270078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 271f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira private CharSequence createChip(RecipientEntry entry) { 272078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira // We want to override the tokenizer behavior with our own ending 273078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira // token, space. 274f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira SpannableString chipText = new SpannableString(mTokenizer.terminateToken(entry 275f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira .getDisplayName())); 276078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int end = getSelectionEnd(); 277078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 278078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira try { 279f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira chipText.setSpan(constructChipSpan(entry, start, false), 0, entry.getDisplayName() 280f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira .length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 281078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } catch (NullPointerException e) { 282078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira Log.e(TAG, e.getMessage()); 283078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira return null; 284078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 285078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 286078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira return chipText; 287078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 288078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 289078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira @Override 290078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 291078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira submitItemAtPosition(position); 292078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 293078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 294078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira private void submitItemAtPosition(int position) { 295f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position); 296078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira clearComposingText(); 297078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 298078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int end = getSelectionEnd(); 299078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 300078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 301078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira Editable editable = getText(); 302f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira editable.replace(start, end, createChip(entry)); 303078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 304078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 305078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira 3062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira /** 3072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientChip defines an ImageSpan that contains information relevant to 3082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * a particular recipient. 3092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 3102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public class RecipientChip extends ImageSpan implements OnItemClickListener, OnDismissListener { 3112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private final CharSequence mDisplay; 3122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private final CharSequence mValue; 3142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private final int mOffset; 3162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private ListPopupWindow mPopup; 3182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private View mAnchorView; 3202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private int mLeft; 3222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private int mId = -1; 3242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 325f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) { 3262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(drawable); 327f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mDisplay = entry.getDisplayName(); 328f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mValue = entry.getDestination(); 329f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mId = entry.getContactId(); 3302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mOffset = offset; 331f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 3322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mAnchorView = new View(getContext()); 3332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mAnchorView.setLeft(bounds.left); 3342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mAnchorView.setRight(bounds.left); 335f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mAnchorView.setTop(bounds.bottom); 336f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mAnchorView.setBottom(bounds.bottom); 3372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mAnchorView.setVisibility(View.GONE); 3382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void onKeyDown(int keyCode, KeyEvent event) { 3412d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira if (keyCode == KeyEvent.KEYCODE_DEL) { 3422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira if (mPopup != null && mPopup.isShowing()) { 3432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mPopup.dismiss(); 3442d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3452d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira removeChip(); 3462d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3492d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public boolean isCompletedContact() { 3502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return mId != -1; 3512d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3532d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira private void replace(RecipientChip newChip) { 3542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Spannable spannable = getSpannable(); 3552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int spanStart = getChipStart(); 3562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int spanEnd = getChipEnd(); 3572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, ""); 3582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira spannable.removeSpan(this); 3592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira spannable.setSpan(newChip, spanStart, spanEnd, 0); 3602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void removeChip() { 3632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira Spannable spannable = getSpannable(); 3642d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int spanStart = getChipStart(); 3652d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int spanEnd = getChipEnd(); 3662d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, ""); 3672d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira spannable.removeSpan(this); 3682d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira spannable.setSpan(null, spanStart, spanEnd, 0); 3692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira onChipChanged(); 3702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public int getChipStart() { 3732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return getSpannable().getSpanStart(this); 3742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public int getChipEnd() { 3772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return getSpannable().getSpanEnd(this); 3782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void replaceChip(String text) { 3812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira clearComposingText(); 3822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 383078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira RecipientChip newChipSpan = null; 384078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira try { 385f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira newChipSpan = constructChipSpan(RecipientEntry.constructFakeEntry(text), 386f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mOffset, false); 387078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } catch (NullPointerException e) { 388078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira Log.e(TAG, e.getMessage()); 389078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira return; 390078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira } 3912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira replace(newChipSpan); 3922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira if (mPopup != null && mPopup.isShowing()) { 3932d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mPopup.dismiss(); 3942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira onChipChanged(); 3962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public CharSequence getDisplay() { 3992d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return mDisplay; 4002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 4022d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public CharSequence getValue() { 4032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira return mValue; 4042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 4062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void onClick(View widget) { 407f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira if (isCompletedContact()) { 408f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mPopup = new ListPopupWindow(widget.getContext()); 409f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 410f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira if (!mPopup.isShowing()) { 411f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mAnchorView.setLeft(mLeft); 412f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mAnchorView.setRight(mLeft); 413f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mPopup.setAnchorView(mAnchorView); 414f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mPopup.setAdapter(getAdapter()); 415f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // TODO: get width from dimen.xml. 416f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mPopup.setWidth(getWidth()); 417f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mPopup.setOnItemClickListener(this); 418f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mPopup.setOnDismissListener(this); 419f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira mPopup.show(); 420f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 421f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } else { 422f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // TODO: move the cursor to the end of the view. Add the text 423f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira // that was in this span to the end of the view as well. 4242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 4272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 4282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, 4292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira int y, int bottom, Paint paint) { 4302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mLeft = (int) x; 4312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super.draw(canvas, text, start, end, x, top, y, bottom, paint); 4322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 4342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 4352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { 4362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mPopup.dismiss(); 4372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira clearComposingText(); 438f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira RecipientEntry entry = (RecipientEntry) adapterView.getItemAtPosition(position); 4392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira replaceChip(entry.getDisplayName()); 4402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4412d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 4422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira // When the popup dialog is dismissed, return the cursor to the end. 4432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira @Override 4442d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public void onDismiss() { 4452d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira mHandler.post(mDelayedSelectionMode); 4462d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira} 449