RecipientEditTextView.java revision 045e80b59ef5e9a709b6e5843d2301a02e0872f2
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; 20c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap; 211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory; 22c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas; 231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix; 24c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Paint; 25c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 30c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 33c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 34c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 35c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 36c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 37c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 38c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode.Callback; 48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereiraimport android.widget.AutoCompleteTextView.Validator; 51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 53b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 54b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 56c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 57c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 58c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.ArrayList; 590f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira 602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView 654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira implements OnItemClickListener, Callback { 66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private static final int INVALID_CONTACT = -1; 738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct size from UX. 754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final float MORE_WIDTH_FACTOR = 0.25f; 764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 77c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 78c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 79c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackgroundPressed; 86c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 87c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 88c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 89c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipDeleteWidth; 90c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesSelectedLayout; 940f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira 951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private int mMoreString; 1004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 103e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipHeight; 104e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 105e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipFontSize; 106e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 1078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private Validator mValidator; 1088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 109045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira private Drawable mInvalidChipBackground; 110045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 1112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 1122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 113d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira setSuggestionsEnabled(false); 114c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 1154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 116c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira // When the user starts typing, make sure we unselect any selected 117c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira // chips. 118c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira addTextChangedListener(new TextWatcher() { 119c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira @Override 120c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira public void afterTextChanged(Editable s) { 121c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira // Do nothing. 122c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira } 123c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira @Override 124c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 125c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira // Do nothing. 126c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira } 127c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira @Override 128c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 129fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira // TODO: find a better way to unfocus a chip when a user starts typing. 130c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira } 131c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira }); 1324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 1334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 1354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 1364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 1374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 13883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Spannable span = getSpannable(); 13983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = getText().length(); 14083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class); 14183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (chips != null && chips.length > 0) { 1424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (chips != null && chips.length > 0) { 1434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // Grab the last chip and set the cursor to after it. 144fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira setSelection(Math.min(chips[chips.length - 1].getChipEnd() + 1, textLength)); 1454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 146d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 147d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 148d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 149d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 150d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 151d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 152d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 1534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 1544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 1554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 1564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 157d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onFocusChanged(hasFocus, direction, previous); 1582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 1592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 1614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mSelectedChip != null) { 1624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 1634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 1644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira commitDefault(); 1654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = createMoreChip(); 1674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 1704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 1714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 1724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 1734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 1744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 177e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 1781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, TextUtils.TruncateAt.END); 1791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 181e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 1821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 1831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 1841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 185e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 1861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 1871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 1881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 1891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 1911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 1921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 1931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 1941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 195c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 1971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 1981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 1991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 2001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 2011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 2021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 2031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Align the display text with where the user enters text. 2041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height 205e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira - Math.abs(height - mChipFontSize)/2, paint); 2061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 2071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.setBounds(width - deleteWidth, 0, width, height); 2081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 2091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 2101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 2111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 2121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 2131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 215045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 216045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 217045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 218045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 219045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 220045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira return mValidator != null && mValidator.isValid(contact.getDestination()) ? 221045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackground : mInvalidChipBackground; 222045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 223045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 224e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 225c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 226c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 228e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 2291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 2301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 2311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 2321a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 2331a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 2341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 2351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 2361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 237c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 241045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 242045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 243045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 244045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 2451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 2469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 2478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira if (contact.getContactId() != INVALID_CONTACT) { 2489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 24990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 25090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 25190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 25290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 25390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 25490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 25590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 25690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 25790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 2589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 2599024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 2609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 2619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 2629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 2639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 2649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 2659024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 2669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Matrix matrix = new Matrix(); 2679024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 2689024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira RectF dst = new RectF(0, 0, iconWidth, height); 2699024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER); 2709024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 2729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 2739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 274c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 2751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 2761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Align the display text with where the user enters text. 2771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding + iconWidth, 27890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira height - Math.abs(height - mChipFontSize) / 2, paint); 279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 2801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 2811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 2821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 2831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 2851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 2861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 2871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 2881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 2891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 2911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 2921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int line = layout.getLineForOffset(offset); 2931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int lineTop = layout.getLineTop(line); 2941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 2951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 2961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 2971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 2981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 299e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 3011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 302045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 3031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Get the location of the widget so we can properly offset 306c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the anchor for each chip. 307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int[] xy = new int[2]; 308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira getLocationOnScreen(xy); 309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 3111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 3121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + tmpBitmap.getWidth(), 313e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira calculateLineBottom(xy[1], line, tmpBitmap.getHeight())); 314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds); 315045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira recipientChip 316045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira .setIsValid(mValidator != null && mValidator.isValid(contact.getDestination())); 317c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 3212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 3238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 324045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 325045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 3268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * 2) the height of a line in the autocomplete view vs the heigt of a chip 3278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * 3) padding built into the edit text view will move the bottom position 3288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * 4) the position of the autocomplete view on the screen, taking into account 3298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that any top padding will move this down visually 3308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 331e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private int calculateLineBottom(int yOffset, int line, int chipHeight) { 332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int bottomPadding = 0; 333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (line == getLineCount() - 1) { 334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira bottomPadding += getPaddingBottom(); 335c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 336e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira return ((line + 1) * getLineHeight()) + yOffset + getPaddingTop() 337e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira + (chipHeight - getLineHeight()) + bottomPadding; 338f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 339f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 3408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 3418684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 3428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 3438684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 3448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 345c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 3461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 3472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Set all chip dimensions and resources. This has to be done from the application 351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * as this is a static library. 352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param chipBackground drawable 3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesSelectedLayout 358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 359045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @deprecated 360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 36143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 3624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable chipDelete, Bitmap defaultContact, int moreResource, int alternatesLayout, 363e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int alternatesSelectedLayout, float chipHeight, float padding, float chipFontSize) { 364c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mChipBackground = chipBackground; 365c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mChipDelete = chipDelete; 367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mChipPadding = (int) padding; 368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAlternatesLayout = alternatesLayout; 369c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAlternatesSelectedLayout = alternatesSelectedLayout; 3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mDefaultContactPhoto = defaultContact; 3714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreString = moreResource; 372e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mChipHeight = chipHeight; 373e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mChipFontSize = chipFontSize; 374c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 375c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 376045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 377045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 378045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int alternatesLayout, int alternatesSelectedLayout, float chipHeight, float padding, 379045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira float chipFontSize) { 380045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackground = chipBackground; 381045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 382045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipDelete = chipDelete; 383045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipPadding = (int) padding; 384045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mAlternatesLayout = alternatesLayout; 385045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mAlternatesSelectedLayout = alternatesSelectedLayout; 386045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mDefaultContactPhoto = defaultContact; 387045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mMoreString = moreResource; 388045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipHeight = chipHeight; 389045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipFontSize = chipFontSize; 390045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mInvalidChipBackground = invalidChip; 391045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 392045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 3998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 4008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 4018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 4028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 4038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 4048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 4058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 4068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 4078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 4088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 4098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 4168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 4178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 41995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 42095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 42195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 42295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 42395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 42495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 42595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 4268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 4278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 4288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 4298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 4308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 4318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 4328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 4338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 43495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 435c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 436c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 437c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 438c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 439c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_TAB: 440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 441d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 442c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 44595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 450045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 451045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 452045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 453045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 454045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 455045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 456045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 4578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 4584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 4594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira boolean enough = enoughToFilter(); 4604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira boolean shouldSubmitAtPosition = false; 461045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 4624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 4634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (enough) { 4644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 4654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if ((chips == null || chips.length == 0)) { 4664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // There's something being filtered or typed that has not been 4674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // completed yet. 4684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shouldSubmitAtPosition = true; 4694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 4704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 4714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 4724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (shouldSubmitAtPosition) { 4734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (getAdapter().getCount() > 0) { 4744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // choose the first entry. 4754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira submitItemAtPosition(0); 476d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 4774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 4784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 4794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira String text = editable.toString().substring(start, end); 4804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearComposingText(); 48190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 4824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 4834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 484fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.replace(start, end, createChip(entry, false)); 4854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira dismissDropDown(); 4864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 4874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 488d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 489d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 4904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 491d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 492d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 4938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 4948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 4958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 4968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip.onKeyDown(keyCode, event); 501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 5082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Spannable getSpannable() { 511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return (Spannable) getText(); 512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 522c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 523c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 526c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 527c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 528c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 531c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 532c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 533c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 534c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 535c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 536c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 537c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 538c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip.unselectChip(); 540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 541c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 54236d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 543c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 5478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 5488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 5498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 5508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 5518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 5528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 555d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 556d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 557d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 558d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 559d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 560c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 561d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 563c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { 565c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 573c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = currentChip.selectChip(); 574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 5758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // Selection may have moved due to the tap event, 5768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // but make sure we correctly reset selection to the 5778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // end so that any unfinished chips are committed. 5788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 5798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = currentChip.selectChip(); 581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip.onClick(this, offset, x, y); 583c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 588c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 594c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 596c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 597c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 598c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 600c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 601c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 602c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 603c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 604c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 605c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 606c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 607c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 608c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 609c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 610c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 6114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 6124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 6134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 614c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 615c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 6164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 617c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 618c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 619c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 620c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 622c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 623c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 6244fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 6254fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 6264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 6274fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 6284fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 6294fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 6304fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 631c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 632c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 633c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 634c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 635c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 636c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chip.matchesChip(offset)) { 637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 638c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 639c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 640c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 642c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 643fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira CharSequence displayText = mTokenizer.terminateToken(entry.getDestination()); 645c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 646fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int textLength = displayText.length()-1; 647c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 648c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 649c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 650c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 651fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira chipText.setSpan(constructChipSpan(entry, start, pressed), 0, textLength, 652c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 653c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 654c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 656c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 657c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 658c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 659c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 660c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 6618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 6628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 6638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 6648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 665c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 666c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 667c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 668c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 669c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 670c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 671c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position); 6729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // If the display name and the address are the same, then make this 6739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // a fake recipient that is editable. 6749024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (TextUtils.equals(entry.getDisplayName(), entry.getDestination())) { 6759024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira entry = RecipientEntry.constructFakeEntry(entry.getDestination()); 6769024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 677c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 678c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 679c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 680c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 681c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 682c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 683c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 684fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.replace(start, end, createChip(entry, false)); 685c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 686c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 687c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 688c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 689c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 6907a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 6917a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 6927a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 6937a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 6947a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 695c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 696c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 697c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 698c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 69983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 70083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 70183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 70283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 703c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 704c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 705c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 70683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 7077a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 7087a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 7097a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 7107a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 711c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 712c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 713c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 714c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 7184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 7194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 7234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 7274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 7284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 7328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 7334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 7354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 7364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 739045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 740045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 741045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 7428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 7434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan createMoreChip() { 74483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] recipients = getRecipients(); 74583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 7464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return null; 7474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 74883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 7494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 7504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 7514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get the correct size from visual design. 7524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int width = (int) Math.floor(getWidth() * MORE_WIDTH_FACTOR); 7534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 7544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 7554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 7564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira String moreText = getResources().getString(mMoreString, overage); 7574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0), 7584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira getPaint()); 7594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 7614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 7624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira ImageSpan moreSpan = new ImageSpan(result); 7634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable spannable = getSpannable(); 7644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 7654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, text.length(), RecipientChip.class); 7664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (chips == null || chips.length == 0) { 7674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 7684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 7694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return null; 7704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 771045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(chips.length); 7724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 7734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 7744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (int i = numRecipients - overage; i < chips.length; i++) { 7754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.add(chips[i]); 7769024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 7779024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira totalReplaceStart = chips[i].getChipStart(); 7789024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 7799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == chips.length - 1) { 7809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira totalReplaceEnd = chips[i].getChipEnd(); 7819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 7829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira chips[i].setPreviousChipStart(chips[i].getChipStart()); 7839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira chips[i].setPreviousChipEnd(chips[i].getChipEnd()); 7844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira spannable.removeSpan(chips[i]); 7854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(totalReplaceStart, 7874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira totalReplaceEnd)); 7884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 7894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira text.replace(totalReplaceStart, totalReplaceEnd, chipText); 7904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return moreSpan; 7914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 7958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 7968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 7974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void removeMoreChip() { 7984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 7994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 8004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 8014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 8024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 8034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 8044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 8054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 8064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira SpannableString associatedText; 8074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 8089024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int chipStart = chip.getPreviousChipStart(); 8099024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int chipEnd = chip.getPreviousChipEnd(); 8104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira associatedText = new SpannableString(editable.subSequence(chipStart, chipEnd)); 8114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira associatedText.setSpan(chip, 0, associatedText.length(), 8124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 8134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira editable.replace(chipStart, chipEnd, associatedText); 8144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 8164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 820c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * RecipientChip defines an ImageSpan that contains information relevant to 822c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * a particular recipient. 823c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 824c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira public class RecipientChip extends ImageSpan implements OnItemClickListener { 825c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final CharSequence mDisplay; 826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 827c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final CharSequence mValue; 828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private View mAnchorView; 830c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mLeft; 832c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 833c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final long mContactId; 834c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 835c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final long mDataId; 836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 837c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientEntry mEntry; 838c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private boolean mSelected = false; 840c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 841c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientAlternatesAdapter mAlternatesAdapter; 842c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 843c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Rect mBounds; 844c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private int mStart = -1; 84683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 8474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private int mEnd = -1; 8484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 849fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private ListPopupWindow mAlternatesPopup; 8508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean mValid = true; 8528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 853c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) { 854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super(drawable); 855c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mDisplay = entry.getDisplayName(); 856c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mValue = entry.getDestination(); 857c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mContactId = entry.getContactId(); 858c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mDataId = entry.getDataId(); 859c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mEntry = entry; 860c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mBounds = bounds; 861c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 862c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView = new View(getContext()); 863c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setLeft(bounds.left); 864c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setRight(bounds.left); 865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setTop(bounds.bottom); 866c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setBottom(bounds.bottom); 867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setVisibility(View.GONE); 868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 869c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setIsValid(boolean isValid) { 8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValid = isValid; 8729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 8739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira 8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Store the offset in the spannable where this RecipientChip 8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is currently being displayed. 8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 8789024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira public void setPreviousChipStart(int start) { 8799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira mStart = start; 8809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 8819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira 8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the offset in the spannable where this RecipientChip 8848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * was currently being displayed. Use this to determine where 8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to place a RecipientChip that has been hidden when the 8868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * RecipientEditTextView loses focus. 8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public int getPreviousChipStart() { 8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira return mStart; 8908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Store the end offset in the spannable where this RecipientChip 8948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is currently being displayed. 8958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 8969024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira public void setPreviousChipEnd(int end) { 8979024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira mEnd = end; 8989024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 8999024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira 9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the end offset in the spannable where this RecipientChip 9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * was currently being displayed. Use this to determine where 9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to place a RecipientChip that has been hidden when the 9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * RecipientEditTextView loses focus. 9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 9068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public int getPreviousChipEnd() { 9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira return mEnd; 9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 9108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 911045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 912045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * the chip without a delete icon and with an unfocused background. This 913045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * is called when the RecipientChip not longer has focus. 9148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void unselectChip() { 916fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int start = getChipStart(); 917fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int end = getChipEnd(); 918fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 91983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 92083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Log.e(TAG, "The chip being unselected no longer exists but should."); 92183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 92283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira getSpannable().removeSpan(this); 92383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 92483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira editable.replace(start, end, createChip(mEntry, false)); 92583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 926fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira mSelectedChip = null; 92783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira clearSelectedChip(); 928fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(true); 929fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setSelection(editable.length()); 930fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 931fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 9328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 933045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Show this chip as selected. If the RecipientChip is just an email address, 9348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * selecting the chip will take the contents of the chip and place it at 9358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 9368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 9378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will change the background color of the chip, show the delete icon, 9388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * and a popup window with the address in use highlighted and any other 9398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * alternate addresses for the contact. 9408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 9418684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * just contained an email address. 9428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 943fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira public RecipientChip selectChip() { 9448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira if (mEntry.getContactId() != INVALID_CONTACT) { 945fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int start = getChipStart(); 946fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int end = getChipEnd(); 947fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira getSpannable().removeSpan(this); 948fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira RecipientChip newChip; 949fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira CharSequence displayText = mTokenizer.terminateToken(mEntry.getDestination()); 950fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira // Always leave a blank space at the end of a chip. 95183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = displayText.length() - 1; 952fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira SpannableString chipText = new SpannableString(displayText); 953fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira try { 954fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira newChip = constructChipSpan(mEntry, start, true); 955fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira chipText.setSpan(newChip, 0, textLength, 956fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 957fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } catch (NullPointerException e) { 958fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Log.e(TAG, e.getMessage(), e); 959fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira return null; 960fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 961fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 962fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 96383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 96483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 96583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 96683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira editable.replace(start, end, chipText); 96783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 968fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(false); 969fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira newChip.setSelected(true); 970fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira newChip.showAlternates(); 971fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(false); 972fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira return newChip; 973fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } else { 974fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira CharSequence text = getValue(); 975fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 976fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira removeChip(); 977fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.append(text); 978fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(true); 979fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setSelection(editable.length()); 980fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira return null; 981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 982c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 983c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 985045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Handle key events for a chip. When the keyCode equals 986045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * KeyEvent.KEYCODE_DEL, this deletes the currently selected chip. 9878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 988c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onKeyDown(int keyCode, KeyEvent event) { 989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_DEL) { 990e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 991e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.dismiss(); 992c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 993c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira removeChip(); 994c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 995c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Remove this chip and any text associated with it from the RecipientEditTextView. 9998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 10008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private void removeChip() { 1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable spannable = getSpannable(); 10029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int spanStart = spannable.getSpanStart(this); 10039024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int spanEnd = spannable.getSpanEnd(this); 1004c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int toDelete = spanEnd; 10069024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira boolean wasSelected = this == mSelectedChip; 10079024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Clear that there is a selected chip before updating any text. 10089024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (wasSelected) { 10099024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira mSelectedChip = null; 10109024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1011c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always remove trailing spaces when removing a chip. 101255bb2833b29945c08b809408ff94ddf7703e911aMindy Pereira while (toDelete >= 0 && toDelete < text.length() - 1 && text.charAt(toDelete) == ' ') { 1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira toDelete++; 1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira spannable.removeSpan(this); 1016c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira text.delete(spanStart, toDelete); 10179024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (wasSelected) { 101836d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira clearSelectedChip(); 101936d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira } 1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the start offset of this chip in the view. 10248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1025c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public int getChipStart() { 10269024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira return getSpannable().getSpanStart(this); 1027c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the end offset of this chip in the view. 10318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1032c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public int getChipEnd() { 10339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira return getSpannable().getSpanEnd(this); 1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace this currently selected chip with a new chip 10388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that uses the contact data provided. 10398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1040c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void replaceChip(RecipientEntry entry) { 1041fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira boolean wasSelected = this == mSelectedChip; 1042fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira if (wasSelected) { 1043fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira mSelectedChip = null; 1044c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1045fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int start = getSpannable().getSpanStart(this); 1046fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int end = getSpannable().getSpanEnd(this); 1047fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira getSpannable().removeSpan(this); 1048fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1049fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira CharSequence chipText = createChip(entry, false); 1050fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira if (start == -1 || end == -1) { 1051fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1052fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.insert(0, chipText); 1053c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1054fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.replace(start, end, chipText); 1055fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1056fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(true); 1057fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira if (wasSelected) { 1058fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira clearSelectedChip(); 1059c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1060c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1061c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Show all addresses associated with a contact. 10648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1065c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void showAlternates() { 1066fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira mAlternatesPopup = new ListPopupWindow(getContext()); 1067c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1068e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira if (!mAlternatesPopup.isShowing()) { 1069c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAlternatesAdapter = new RecipientAlternatesAdapter( 1070e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira getContext(), 1071c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mEntry.getContactId(), mEntry.getDataId(), 1072c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAlternatesLayout, mAlternatesSelectedLayout); 1073c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setLeft(mLeft); 1074c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setRight(mLeft); 1075e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setAnchorView(mAnchorView); 1076e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setAdapter(mAlternatesAdapter); 1077e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setWidth(getWidth()); 1078e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setOnItemClickListener(this); 1079e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.show(); 1080c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1081c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void setSelected(boolean selected) { 1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelected = selected; 1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the text displayed in the chip. 10898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public CharSequence getDisplay() { 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mDisplay; 1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the text value this chip represents. 10968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public CharSequence getValue() { 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mValue; 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * See if a touch event was inside the delete target of 11038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a selected chip. It is in the delete target if: 11048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * 1) the x and y points of the event are within the 11058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * delete assset. 11068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * 2) the point tapped would have caused a cursor to appear 11078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * right after the selected chip. 11088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private boolean isInDelete(int offset, float x, float y) { 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Figure out the bounds of this chip and whether or not 1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the user clicked in the X portion. 1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mSelected 1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira && (offset == getChipEnd() 1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira || (x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right)); 1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Return whether this chip contains the position passed in. 11198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean matchesChip(int offset) { 1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = getChipStart(); 1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getChipEnd(); 1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return (offset >= start && offset <= end); 1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1127045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1128045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1129045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Otherwise, unselect the chip. 11308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onClick(View widget, int offset, float x, float y) { 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelected) { 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (isInDelete(offset, x, y)) { 1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira removeChip(); 11351a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira } else { 11361a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira clearSelectedChip(); 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, 1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int y, int bottom, Paint paint) { 1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Shift the bounds of this span to where it is actually drawn on the screeen. 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mLeft = (int) x; 1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.draw(canvas, text, start, end, x, top, y, bottom, paint); 1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1150045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Handle clicks to alternate addresses for a selected chip. If the user 11518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * selects an alternate, the chip is replaced with a new contact with the 11528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * new contact address information. 11538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { 1156e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.dismiss(); 1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira replaceChip(mAlternatesAdapter.getRecipientEntry(position)); 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the id of the contact associated with this chip. 11638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public long getContactId() { 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mContactId; 1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the id of the data associated with this chip. 11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public long getDataId() { 1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mDataId; 1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 11742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 11752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira} 1176