RecipientEditTextView.java revision 4f7412c084ad344e94a320b51270ac6480a47a84
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 /** 3504f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 3514f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 3524f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 3544f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesSelectedLayout 359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 36143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 362045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 363045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int alternatesLayout, int alternatesSelectedLayout, float chipHeight, float padding, 364045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira float chipFontSize) { 365045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackground = chipBackground; 366045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 367045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipDelete = chipDelete; 368045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipPadding = (int) padding; 369045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mAlternatesLayout = alternatesLayout; 370045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mAlternatesSelectedLayout = alternatesSelectedLayout; 371045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mDefaultContactPhoto = defaultContact; 372045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mMoreString = moreResource; 373045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipHeight = chipHeight; 374045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipFontSize = chipFontSize; 375045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mInvalidChipBackground = invalidChip; 376045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 377045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 378c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 380c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 382c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 3848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 3858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 3868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 3878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 3888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 3898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 3908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 3918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 3928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 3938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 3948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 4018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 4028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 40495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 40595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 40695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 40795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 40895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 40995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 41095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 4118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 4128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 4138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 4148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 4158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 4168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 4178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 4188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 41995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 423c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_TAB: 425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 426d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 43095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 431c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 435045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 436045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 437045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 438045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 439045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 440045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 4428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 4434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 4444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira boolean enough = enoughToFilter(); 4454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira boolean shouldSubmitAtPosition = false; 446045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 4474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 4484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (enough) { 4494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 4504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if ((chips == null || chips.length == 0)) { 4514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // There's something being filtered or typed that has not been 4524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // completed yet. 4534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shouldSubmitAtPosition = true; 4544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 4554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 4564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 4574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (shouldSubmitAtPosition) { 4584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (getAdapter().getCount() > 0) { 4594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // choose the first entry. 4604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira submitItemAtPosition(0); 461d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 4624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 4634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 4644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira String text = editable.toString().substring(start, end); 4654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearComposingText(); 46690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 4674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 4684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 469fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.replace(start, end, createChip(entry, false)); 4704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira dismissDropDown(); 4714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 4724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 473d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 474d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 4754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 476d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 477d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 4788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 4798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 4808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 4818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 482c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 483c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 485c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip.onKeyDown(keyCode, event); 486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 489c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 490c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 492c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 4932d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 495c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Spannable getSpannable() { 496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return (Spannable) getText(); 497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 522c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 523c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip.unselectChip(); 525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 526c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 52736d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 528c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 5328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 5338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 5348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 5358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 5368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 5378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 538c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 540d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 541d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 542d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 543d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 544d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 546d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { 550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 552c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 556c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = currentChip.selectChip(); 559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 5608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // Selection may have moved due to the tap event, 5618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // but make sure we correctly reset selection to the 5628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // end so that any unfinished chips are committed. 5638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 5648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 565c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = currentChip.selectChip(); 566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip.onClick(this, offset, x, y); 568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 573c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 576c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 583c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 588c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 594c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5964fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 5974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 5984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 600c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 6014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 602c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 603c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 604c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 605c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 606c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 607c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 608c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 6094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 6104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 6114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 6124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 6134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 6144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 6154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 616c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 617c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 618c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 619c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 620c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chip.matchesChip(offset)) { 622c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 623c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 624c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 625c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 626c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 627c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 628fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 629c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira CharSequence displayText = mTokenizer.terminateToken(entry.getDestination()); 630c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 631fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int textLength = displayText.length()-1; 632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 633c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 634c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 635c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 636fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira chipText.setSpan(constructChipSpan(entry, start, pressed), 0, textLength, 637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 638c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 639c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 640c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 642c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 643c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 645c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 6468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 6478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 6488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 6498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 650c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 651c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 652c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 653c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 654c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 656c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position); 6579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // If the display name and the address are the same, then make this 6589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // a fake recipient that is editable. 6599024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (TextUtils.equals(entry.getDisplayName(), entry.getDestination())) { 6609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira entry = RecipientEntry.constructFakeEntry(entry.getDestination()); 6619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 662c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 663c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 664c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 665c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 666c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 667c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 668c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 669fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.replace(start, end, createChip(entry, false)); 670c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 671c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 672c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 673c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 674c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 6757a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 6767a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 6777a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 6787a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 6797a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 680c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 681c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 682c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 683c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 68483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 68583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 68683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 68783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 688c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 689c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 690c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 69183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 6927a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 6937a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 6947a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 6957a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 696c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 697c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 698c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 699c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 7034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 7044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 7084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 7124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 7134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 7178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 7184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 7194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 7204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 7214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 724045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 725045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 726045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 7278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 7284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan createMoreChip() { 72983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] recipients = getRecipients(); 73083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 7314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return null; 7324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 73383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 7344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 7354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 7364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get the correct size from visual design. 7374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int width = (int) Math.floor(getWidth() * MORE_WIDTH_FACTOR); 7384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 7394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 7404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 7414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira String moreText = getResources().getString(mMoreString, overage); 7424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0), 7434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira getPaint()); 7444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 7464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 7474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira ImageSpan moreSpan = new ImageSpan(result); 7484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable spannable = getSpannable(); 7494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 7504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, text.length(), RecipientChip.class); 7514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (chips == null || chips.length == 0) { 7524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 7534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 7544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return null; 7554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 756045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(chips.length); 7574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 7584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 7594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (int i = numRecipients - overage; i < chips.length; i++) { 7604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.add(chips[i]); 7619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 7629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira totalReplaceStart = chips[i].getChipStart(); 7639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 7649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == chips.length - 1) { 7659024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira totalReplaceEnd = chips[i].getChipEnd(); 7669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 7679024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira chips[i].setPreviousChipStart(chips[i].getChipStart()); 7689024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira chips[i].setPreviousChipEnd(chips[i].getChipEnd()); 7694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira spannable.removeSpan(chips[i]); 7704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(totalReplaceStart, 7724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira totalReplaceEnd)); 7734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 7744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira text.replace(totalReplaceStart, totalReplaceEnd, chipText); 7754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return moreSpan; 7764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 7774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 7788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 7808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 7818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 7824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void removeMoreChip() { 7834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 7844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 7854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 7864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 7874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 7884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 7894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 7904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 7914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira SpannableString associatedText; 7924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 7939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int chipStart = chip.getPreviousChipStart(); 7949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int chipEnd = chip.getPreviousChipEnd(); 7954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira associatedText = new SpannableString(editable.subSequence(chipStart, chipEnd)); 7964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira associatedText.setSpan(chip, 0, associatedText.length(), 7974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 7984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira editable.replace(chipStart, chipEnd, associatedText); 7994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 8014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 8044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * RecipientChip defines an ImageSpan that contains information relevant to 807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * a particular recipient. 808c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 809c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira public class RecipientChip extends ImageSpan implements OnItemClickListener { 810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final CharSequence mDisplay; 811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 812c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final CharSequence mValue; 813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private View mAnchorView; 815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mLeft; 817c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 818c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final long mContactId; 819c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 820c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private final long mDataId; 821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 822c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientEntry mEntry; 823c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 824c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private boolean mSelected = false; 825c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientAlternatesAdapter mAlternatesAdapter; 827c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Rect mBounds; 829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private int mStart = -1; 83183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 8324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private int mEnd = -1; 8334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 834fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private ListPopupWindow mAlternatesPopup; 8358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean mValid = true; 8378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 838c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) { 839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super(drawable); 840c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mDisplay = entry.getDisplayName(); 841c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mValue = entry.getDestination(); 842c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mContactId = entry.getContactId(); 843c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mDataId = entry.getDataId(); 844c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mEntry = entry; 845c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mBounds = bounds; 846c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 847c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView = new View(getContext()); 848c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setLeft(bounds.left); 849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setRight(bounds.left); 850c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setTop(bounds.bottom); 851c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setBottom(bounds.bottom); 852c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setVisibility(View.GONE); 853c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setIsValid(boolean isValid) { 8568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValid = isValid; 8579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 8589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira 8598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Store the offset in the spannable where this RecipientChip 8618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is currently being displayed. 8628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 8639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira public void setPreviousChipStart(int start) { 8649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira mStart = start; 8659024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 8669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira 8678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the offset in the spannable where this RecipientChip 8698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * was currently being displayed. Use this to determine where 8708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to place a RecipientChip that has been hidden when the 8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * RecipientEditTextView loses focus. 8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public int getPreviousChipStart() { 8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira return mStart; 8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Store the end offset in the spannable where this RecipientChip 8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is currently being displayed. 8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 8819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira public void setPreviousChipEnd(int end) { 8829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira mEnd = end; 8839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 8849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira 8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the end offset in the spannable where this RecipientChip 8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * was currently being displayed. Use this to determine where 8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to place a RecipientChip that has been hidden when the 8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * RecipientEditTextView loses focus. 8908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 8918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public int getPreviousChipEnd() { 8928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira return mEnd; 8938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 896045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 897045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * the chip without a delete icon and with an unfocused background. This 898045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * is called when the RecipientChip not longer has focus. 8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 900c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void unselectChip() { 901fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int start = getChipStart(); 902fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int end = getChipEnd(); 903fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 90483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 90583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Log.e(TAG, "The chip being unselected no longer exists but should."); 90683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 90783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira getSpannable().removeSpan(this); 90883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 90983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira editable.replace(start, end, createChip(mEntry, false)); 91083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 911fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira mSelectedChip = null; 91283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira clearSelectedChip(); 913fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(true); 914fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setSelection(editable.length()); 915fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 916fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 9178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 918045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Show this chip as selected. If the RecipientChip is just an email address, 9198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * selecting the chip will take the contents of the chip and place it at 9208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 9218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 9228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will change the background color of the chip, show the delete icon, 9238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * and a popup window with the address in use highlighted and any other 9248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * alternate addresses for the contact. 9258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 9268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * just contained an email address. 9278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 928fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira public RecipientChip selectChip() { 9298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira if (mEntry.getContactId() != INVALID_CONTACT) { 930fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int start = getChipStart(); 931fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int end = getChipEnd(); 932fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira getSpannable().removeSpan(this); 933fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira RecipientChip newChip; 934fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira CharSequence displayText = mTokenizer.terminateToken(mEntry.getDestination()); 935fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira // Always leave a blank space at the end of a chip. 93683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = displayText.length() - 1; 937fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira SpannableString chipText = new SpannableString(displayText); 938fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira try { 939fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira newChip = constructChipSpan(mEntry, start, true); 940fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira chipText.setSpan(newChip, 0, textLength, 941fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 942fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } catch (NullPointerException e) { 943fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Log.e(TAG, e.getMessage(), e); 944fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira return null; 945fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 946fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 947fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 94883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 94983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 95083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 95183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira editable.replace(start, end, chipText); 95283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 953fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(false); 954fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira newChip.setSelected(true); 955fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira newChip.showAlternates(); 956fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(false); 957fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira return newChip; 958fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } else { 959fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira CharSequence text = getValue(); 960fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 961fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira removeChip(); 962fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.append(text); 963fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(true); 964fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setSelection(editable.length()); 965fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira return null; 966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 970045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Handle key events for a chip. When the keyCode equals 971045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * KeyEvent.KEYCODE_DEL, this deletes the currently selected chip. 9728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onKeyDown(int keyCode, KeyEvent event) { 974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_DEL) { 975e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 976e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.dismiss(); 977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira removeChip(); 979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 980c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Remove this chip and any text associated with it from the RecipientEditTextView. 9848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 9858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private void removeChip() { 986c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable spannable = getSpannable(); 9879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int spanStart = spannable.getSpanStart(this); 9889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira int spanEnd = spannable.getSpanEnd(this); 989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 990c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int toDelete = spanEnd; 9919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira boolean wasSelected = this == mSelectedChip; 9929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Clear that there is a selected chip before updating any text. 9939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (wasSelected) { 9949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira mSelectedChip = null; 9959024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always remove trailing spaces when removing a chip. 99755bb2833b29945c08b809408ff94ddf7703e911aMindy Pereira while (toDelete >= 0 && toDelete < text.length() - 1 && text.charAt(toDelete) == ' ') { 998c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira toDelete++; 999c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1000c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira spannable.removeSpan(this); 1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira text.delete(spanStart, toDelete); 10029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (wasSelected) { 100336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira clearSelectedChip(); 100436d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira } 1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1006c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the start offset of this chip in the view. 10098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public int getChipStart() { 10119024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira return getSpannable().getSpanStart(this); 1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the end offset of this chip in the view. 10168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public int getChipEnd() { 10189024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira return getSpannable().getSpanEnd(this); 1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace this currently selected chip with a new chip 10238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that uses the contact data provided. 10248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1025c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void replaceChip(RecipientEntry entry) { 1026fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira boolean wasSelected = this == mSelectedChip; 1027fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira if (wasSelected) { 1028fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira mSelectedChip = null; 1029c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1030fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int start = getSpannable().getSpanStart(this); 1031fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira int end = getSpannable().getSpanEnd(this); 1032fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira getSpannable().removeSpan(this); 1033fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1034fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira CharSequence chipText = createChip(entry, false); 1035fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira if (start == -1 || end == -1) { 1036fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1037fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.insert(0, chipText); 1038c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1039fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira editable.replace(start, end, chipText); 1040fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1041fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira setCursorVisible(true); 1042fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira if (wasSelected) { 1043fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira clearSelectedChip(); 1044c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1045c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1046c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Show all addresses associated with a contact. 10498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1050c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void showAlternates() { 1051fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira mAlternatesPopup = new ListPopupWindow(getContext()); 1052c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1053e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira if (!mAlternatesPopup.isShowing()) { 1054c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAlternatesAdapter = new RecipientAlternatesAdapter( 1055e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira getContext(), 1056c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mEntry.getContactId(), mEntry.getDataId(), 1057c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAlternatesLayout, mAlternatesSelectedLayout); 1058c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setLeft(mLeft); 1059c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mAnchorView.setRight(mLeft); 1060e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setAnchorView(mAnchorView); 1061e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setAdapter(mAlternatesAdapter); 1062e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setWidth(getWidth()); 1063e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.setOnItemClickListener(this); 1064e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.show(); 1065c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1066c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1067c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1068c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void setSelected(boolean selected) { 1069c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelected = selected; 1070c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1071c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the text displayed in the chip. 10748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1075c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public CharSequence getDisplay() { 1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mDisplay; 1077c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the text value this chip represents. 10818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public CharSequence getValue() { 1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mValue; 1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * See if a touch event was inside the delete target of 10888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a selected chip. It is in the delete target if: 10898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * 1) the x and y points of the event are within the 10908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * delete assset. 10918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * 2) the point tapped would have caused a cursor to appear 10928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * right after the selected chip. 10938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private boolean isInDelete(int offset, float x, float y) { 1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Figure out the bounds of this chip and whether or not 1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the user clicked in the X portion. 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mSelected 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira && (offset == getChipEnd() 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira || (x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right)); 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Return whether this chip contains the position passed in. 11048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean matchesChip(int offset) { 1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = getChipStart(); 1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getChipEnd(); 1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return (offset >= start && offset <= end); 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1112045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1113045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1114045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Otherwise, unselect the chip. 11158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onClick(View widget, int offset, float x, float y) { 1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelected) { 1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (isInDelete(offset, x, y)) { 1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira removeChip(); 11201a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira } else { 11211a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira clearSelectedChip(); 1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, 1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int y, int bottom, Paint paint) { 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Shift the bounds of this span to where it is actually drawn on the screeen. 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mLeft = (int) x; 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.draw(canvas, text, start, end, x, top, y, bottom, paint); 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1135045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Handle clicks to alternate addresses for a selected chip. If the user 11368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * selects an alternate, the chip is replaced with a new contact with the 11378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * new contact address information. 11388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { 1141e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira mAlternatesPopup.dismiss(); 1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira replaceChip(mAlternatesAdapter.getRecipientEntry(position)); 1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the id of the contact associated with this chip. 11488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public long getContactId() { 1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mContactId; 1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the id of the data associated with this chip. 11558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public long getDataId() { 1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return mDataId; 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 11592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 11602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira} 1161