RecipientEditTextView.java revision 90081ee88c7eb216ea22f426aa6856e310a867e1
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;
50c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
512d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
52b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
53b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
54c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
56c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
57c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.ArrayList;
580f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView
644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    implements OnItemClickListener, Callback {
65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct size from UX.
724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final float MORE_WIDTH_FACTOR = 0.25f;
734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
74c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
75c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
76c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
77c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
78c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
79c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
86c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipDeleteWidth;
87c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
88c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private ArrayList<RecipientChip> mRecipients;
89c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
90c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesSelectedLayout;
930f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private int mMoreString;
994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
102e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
103e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
104e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
105e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
106e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private ListPopupWindow mAlternatesPopup;
107e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mRecipients = new ArrayList<RecipientChip>();
111e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
112d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        setSuggestionsEnabled(false);
113c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
1144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
115c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // When the user starts typing, make sure we unselect any selected
116c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // chips.
117c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        addTextChangedListener(new TextWatcher() {
118c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
119c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void afterTextChanged(Editable s) {
120c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
121c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
122c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
123c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void onTextChanged(CharSequence s, int start, int before, int count) {
124c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
125c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
126c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
127c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
128fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira                // TODO: find a better way to unfocus a chip when a user starts typing.
129c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
130c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        });
1314fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
1324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
1344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
1354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
1364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
1374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (mRecipients != null && mRecipients.size() > 0) {
1384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            Spannable span = getSpannable();
139fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira            int textLength = getText().length();
140fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira            RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
1414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
1424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
143fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira                setSelection(Math.min(chips[chips.length - 1].getChipEnd() + 1, textLength));
1444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
145d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
146d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
147d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
148d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
149d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
150d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
151d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
1524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
1534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
1544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
1554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
156d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
1572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
1582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
1604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mSelectedChip != null) {
1614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
1624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
1634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            commitDefault();
1644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        mMoreChip = createMoreChip();
1664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
1674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
1694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
1704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
1714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
1724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
1734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
1744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
176e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
1771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth, TextUtils.TruncateAt.END);
1781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
180e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
1811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
1821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
1831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
184e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
1851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
1861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
1871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
1881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
1901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
1911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
1921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
1931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
1961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
1981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
1991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
2001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
2011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
2031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
204e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    - Math.abs(height - mChipFontSize)/2, paint);
2051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
2061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
2071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
2081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
2091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
2101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
2111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
2121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
214e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
217c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
218e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
2191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
2201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
2211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
2221a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
2231a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
2241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
2251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
2261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
228c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
229c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
230c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
2311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground != null) {
2321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackground.setBounds(0, 0, width, height);
2331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackground.draw(canvas);
2341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
2369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (contact.getContactId() != -1) {
2379024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
23890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
23990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
24090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
24190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
24290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
24390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
24490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
24590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
24690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
2479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
2489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
2499024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
2509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
2519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
2529024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
2539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
2549024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
2559024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
2569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
2579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF dst = new RectF(0, 0, iconWidth, height);
2589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
2599024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
260c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
2619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
2629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
2641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
2661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding + iconWidth,
26790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    height - Math.abs(height - mChipFontSize) / 2, paint);
268c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
2691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
2701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
2711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
2721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
273c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
2741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
2751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
2761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
2771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
2781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
2801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
2811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int line = layout.getLineForOffset(offset);
2821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int lineTop = layout.getLineTop(line);
2831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
2851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
2861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
2881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
289e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
2911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
292e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
2931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
294c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Get the location of the widget so we can properly offset
296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // the anchor for each chip.
297c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int[] xy = new int[2];
298c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        getLocationOnScreen(xy);
299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
3011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
3021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + tmpBitmap.getWidth(),
303e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                calculateLineBottom(xy[1], line, tmpBitmap.getHeight()));
304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds);
305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
306c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
3102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // The bottom of the line the chip will be located on is calculated by 4 factors:
313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // 1) which line the chip appears on
314e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    // 2) the height of a line in the autocomplete view vs the heigt of a chip
315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // 3) padding built into the edit text view will move the bottom position
316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // 4) the position of the autocomplete view on the screen, taking into account
317c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // that any top padding will move this down visually
318e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private int calculateLineBottom(int yOffset, int line, int chipHeight) {
319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int bottomPadding = 0;
320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (line == getLineCount() - 1) {
321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            bottomPadding += getPaddingBottom();
322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
323e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        return ((line + 1) * getLineHeight()) + yOffset + getPaddingTop()
324e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                + (chipHeight - getLineHeight()) + bottomPadding;
325f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
326f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // Get the max amount of space a chip can take up. The formula takes into
328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // account the width of the EditTextView, any view padding, and padding
329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // that will be added to the chip.
330c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
3311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
3322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
335c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Set all chip dimensions and resources. This has to be done from the application
336c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * as this is a static library.
337c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param chipBackground drawable
3381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
3391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
3401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
3411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
3421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesSelectedLayout
343c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
344c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
34543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
3464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Drawable chipDelete, Bitmap defaultContact, int moreResource, int alternatesLayout,
347e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            int alternatesSelectedLayout, float chipHeight, float padding, float chipFontSize) {
348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipBackground = chipBackground;
349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipDelete = chipDelete;
351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipPadding = (int) padding;
352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mAlternatesLayout = alternatesLayout;
353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mAlternatesSelectedLayout = alternatesSelectedLayout;
3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        mDefaultContactPhoto = defaultContact;
3554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        mMoreString = moreResource;
356e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        mChipHeight = chipHeight;
357e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        mChipFontSize = chipFontSize;
358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
361c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
362c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
363c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
364c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
365c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // We want to handle replacing text in the onItemClickListener
367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // so we can get all the associated contact information including
368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // display text, address, and id.
369c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
370c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
371c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
372c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
373c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
374c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
37595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
37695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
37795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
37895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
37995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
38095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
38195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
38295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
384c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
385c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
386c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_TAB:
388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
389d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
39395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
398d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    // If the popup is showing, the default is the first item in the popup
399d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    // suggestions list. Otherwise, it is whatever the user had typed in.
400d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    private boolean commitDefault() {
4014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
4024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        boolean enough = enoughToFilter();
4034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        boolean shouldSubmitAtPosition = false;
4044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int end = getSelectionEnd();
4054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
4064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (enough) {
4074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
4084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if ((chips == null || chips.length == 0)) {
4094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // There's something being filtered or typed that has not been
4104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // completed yet.
4114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                shouldSubmitAtPosition = true;
4124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
4134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
4144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
4154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (shouldSubmitAtPosition) {
4164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (getAdapter().getCount() > 0) {
4174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // choose the first entry.
4184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                submitItemAtPosition(0);
419d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
4204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
4214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            } else {
4224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                String text = editable.toString().substring(start, end);
4234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                clearComposingText();
42490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (text != null && text.length() > 0 && !text.equals(" ")) {
4254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
4264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
4274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    editable.replace(start, end, createChip(entry));
4284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    dismissDropDown();
4294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
4304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return false;
431d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
432d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
4334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
434d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
435d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
436c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
437c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
438c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
439c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.onKeyDown(keyCode, event);
440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
441c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
442c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
4472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return (Spannable) getText();
451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
453c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
454c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
462c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
463c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
472c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
473c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
476c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
477c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.unselectChip();
479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
480c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
48136d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
482c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
483c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
485c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
486d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
487d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
488d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
489d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
490d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
492d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
493c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
494c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
495c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip.onClick(this, offset, x, y);
509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
522c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
523c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
526c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
527c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
528c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
531c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
532c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
533c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
534c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
535c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
536c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
5384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
5394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
541c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
543c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5504fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
5514fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
5524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
5534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
5544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
5554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
5564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
558c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
560c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chip.matchesChip(offset)) {
563c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
565c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private CharSequence createChip(RecipientEntry entry) {
570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        CharSequence displayText = mTokenizer.terminateToken(entry.getDestination());
571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int textLength = displayText.length();
573c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (displayText.charAt(textLength - 1) == ' ') {
574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            textLength--;
575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
576c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            displayText = displayText.toString().concat(" ");
577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            textLength = displayText.length();
578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
583c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            chipText.setSpan(constructChipSpan(entry, start, false), 0, textLength,
584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
588c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
594c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
596c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
597c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
598c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position);
6009024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        // If the display name and the address are the same, then make this
6019024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        // a fake recipient that is editable.
6029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        if (TextUtils.equals(entry.getDisplayName(), entry.getDestination())) {
6039024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            entry = RecipientEntry.constructFakeEntry(entry.getDestination());
6049024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
605c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
606c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
607c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
608c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
609c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
610c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
611c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
612e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        editable.replace(start, end, createChip(entry));
613c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
614c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
615c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
616c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
617c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
618c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (RecipientChip chip : mRecipients) {
619c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            result.add(chip.getContactId());
620c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
622c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
623c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
624c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
625c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
626c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
627c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (RecipientChip chip : mRecipients) {
628c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            result.add(chip.getDataId());
629c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
630c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
631c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
6334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
6354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
6374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
6384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
6404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
6414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
6424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
6444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
6454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
6464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
6474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // Prevent selection of chips.
6494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
6504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
6514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
6524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
6534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // The more chip is text that replaces any chips that do not fit in the pre-defined
6554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // available space when the RecipientEditTextView loses focus and is drawn in a
6564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // collapsed fashion.
6574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan createMoreChip() {
6584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mRecipients == null || mRecipients.size() <= CHIP_LIMIT) {
6594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            return null;
6604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
6614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int numRecipients = mRecipients.size();
6624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
6634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
6644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // TODO: get the correct size from visual design.
6654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int width = (int) Math.floor(getWidth() * MORE_WIDTH_FACTOR);
6664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
6674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
6684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
6694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        String moreText = getResources().getString(mMoreString, overage);
6704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
6714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                getPaint());
6724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
6744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
6754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        ImageSpan moreSpan = new ImageSpan(result);
6764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Spannable spannable = getSpannable();
6774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
6784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        RecipientChip[] chips = spannable.getSpans(0, text.length(), RecipientChip.class);
6794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (chips == null || chips.length == 0) {
6804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
6814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                "We have recipients. Tt should not be possible to have zero RecipientChips.");
6824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            return null;
6834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
6844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
6854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
6864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
6874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        for (int i = numRecipients - overage; i < chips.length; i++) {
6884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mRemovedSpans.add(chips[i]);
6899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
6909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                totalReplaceStart = chips[i].getChipStart();
6919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
6929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == chips.length - 1) {
6939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                totalReplaceEnd = chips[i].getChipEnd();
6949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
6959024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            chips[i].setPreviousChipStart(chips[i].getChipStart());
6969024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            chips[i].setPreviousChipEnd(chips[i].getChipEnd());
6974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            spannable.removeSpan(chips[i]);
6984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
6999024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
7004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        for (int i = chips.length - 1; i >= numRecipients - overage; i--) {
7014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mRecipients.remove(i);
7024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
7034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(totalReplaceStart,
7044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                totalReplaceEnd));
7054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
7064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        text.replace(totalReplaceStart, totalReplaceEnd, chipText);
7074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return moreSpan;
7084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // Replace the more chip, if it exists, with all of the recipient chips it had
7114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // replaced when the RecipientEditTextView gains focus.
7124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
7134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
7144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
7154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
7164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
7174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
7184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
7194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
7204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
7214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                SpannableString associatedText;
7224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
7239024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    int chipStart = chip.getPreviousChipStart();
7249024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    int chipEnd = chip.getPreviousChipEnd();
7254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    associatedText = new SpannableString(editable.subSequence(chipStart, chipEnd));
7264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    associatedText.setSpan(chip, 0, associatedText.length(),
7274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
7284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    editable.replace(chipStart, chipEnd, associatedText);
7294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    mRecipients.add(chip);
7304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
7314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
7324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
7334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
7344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
736c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
737c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * RecipientChip defines an ImageSpan that contains information relevant to
738c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * a particular recipient.
739c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
740c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira    public class RecipientChip extends ImageSpan implements OnItemClickListener {
741c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mDisplay;
742c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
743c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mValue;
744c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
745c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final int mOffset;
746c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
747c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private View mAnchorView;
748c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
749c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private int mLeft;
750c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
751c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mContactId;
752c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
753c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mDataId;
754c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
755c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientEntry mEntry;
756c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
757c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean mSelected = false;
758c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
759c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientAlternatesAdapter mAlternatesAdapter;
760c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
761c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private Rect mBounds;
762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        private int mStart = -1;
7644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        private int mEnd = -1;
7654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
766c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) {
767c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super(drawable);
768c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDisplay = entry.getDisplayName();
769c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mValue = entry.getDestination();
770c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mContactId = entry.getContactId();
771c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDataId = entry.getDataId();
772c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mOffset = offset;
773c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mEntry = entry;
774c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mBounds = bounds;
775c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
776c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView = new View(getContext());
777c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setLeft(bounds.left);
778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setRight(bounds.left);
779c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setTop(bounds.bottom);
780c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setBottom(bounds.bottom);
781c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setVisibility(View.GONE);
782c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mRecipients.add(this);
7834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mStart = offset;
7844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Add +1 for comma (?)
7854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mEnd = offset + mValue.length() + 1;
786c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
787c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public int getPreviousChipStart() {
7899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return mStart;
7909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
7919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
7929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public int getPreviousChipEnd() {
7939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return mEnd;
7949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
7959024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
7969024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public void setPreviousChipStart(int start) {
7979024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            mStart = start;
7989024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
7999024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
8009024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public void setPreviousChipEnd(int end) {
8019024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            mEnd = end;
8029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
8039024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
804c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void unselectChip() {
805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (getChipStart() == -1 || getChipEnd() == -1) {
806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mSelectedChip = null;
807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
808c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
809c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip newChipSpan = null;
811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            try {
812c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                newChipSpan = constructChipSpan(mEntry, mOffset, false);
813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } catch (NullPointerException e) {
814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Log.e(TAG, e.getMessage(), e);
815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
817c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replace(newChipSpan);
818e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
819e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.dismiss();
820c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return;
822c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
823c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
824c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onKeyDown(int keyCode, KeyEvent event) {
825c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (keyCode == KeyEvent.KEYCODE_DEL) {
826e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
827e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    mAlternatesPopup.dismiss();
828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                removeChip();
830c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
832c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
833c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public boolean isCompletedContact() {
834c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mContactId != -1;
835c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
837c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void replace(RecipientChip newChip) {
838c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable spannable = getSpannable();
839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int spanStart = getChipStart();
840c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int spanEnd = getChipEnd();
8419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            boolean wasSelected = this == mSelectedChip;
8429024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
8439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                mSelectedChip = null;
8449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
845c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, "");
846c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.removeSpan(this);
847c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mRecipients.remove(this);
848c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.setSpan(newChip, spanStart, spanEnd, 0);
8499024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
8509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                clearSelectedChip();
8519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
852c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
853c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void removeChip() {
855c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable spannable = getSpannable();
8569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            int spanStart = spannable.getSpanStart(this);
8579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            int spanEnd = spannable.getSpanEnd(this);
858c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Editable text = getText();
859c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int toDelete = spanEnd;
8609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            boolean wasSelected = this == mSelectedChip;
8619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Clear that there is a selected chip before updating any text.
8629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
8639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                mSelectedChip = null;
8649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Always remove trailing spaces when removing a chip.
86655bb2833b29945c08b809408ff94ddf7703e911aMindy Pereira            while (toDelete >= 0 && toDelete < text.length() - 1 && text.charAt(toDelete) == ' ') {
867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                toDelete++;
868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
869c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.removeSpan(this);
870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mRecipients.remove(this);
871c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            text.delete(spanStart, toDelete);
8729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
87336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira                clearSelectedChip();
87436d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira            }
875c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
876c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
877c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipStart() {
8789024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return getSpannable().getSpanStart(this);
879c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
880c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
881c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipEnd() {
8829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return getSpannable().getSpanEnd(this);
883c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
884c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
885c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void replaceChip(RecipientEntry entry) {
886c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
887c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
888c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip newChipSpan = null;
889c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            try {
890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                newChipSpan = constructChipSpan(entry, mOffset, false);
891c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } catch (NullPointerException e) {
892c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Log.e(TAG, e.getMessage(), e);
893c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
895c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replace(newChipSpan);
896e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
897e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.dismiss();
898c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
899c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
900c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
901c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public RecipientChip selectChip() {
902c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
903c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip newChipSpan = null;
904c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (isCompletedContact()) {
905c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                try {
906c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    newChipSpan = constructChipSpan(mEntry, mOffset, true);
907c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    newChipSpan.setSelected(true);
908c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                } catch (NullPointerException e) {
909c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Log.e(TAG, e.getMessage(), e);
910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    return newChipSpan;
911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
912c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                replace(newChipSpan);
913e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
914e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    mAlternatesPopup.dismiss();
915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
916c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mSelected = true;
917c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                // Make sure we call edit on the new chip span.
918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                newChipSpan.showAlternates();
919d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                setCursorVisible(false);
920c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                CharSequence text = getValue();
922c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                removeChip();
923c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Editable editable = getText();
924c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                editable.append(text);
925d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                setCursorVisible(true);
926d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                setSelection(editable.length());
927c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
928c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return newChipSpan;
929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
930c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
931c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void showAlternates() {
932e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira          //  mAlternatesPopup = new ListPopupWindow(getContext());
933c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
934e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            if (!mAlternatesPopup.isShowing()) {
935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAlternatesAdapter = new RecipientAlternatesAdapter(
936e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                        getContext(),
937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mEntry.getContactId(), mEntry.getDataId(),
938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mAlternatesLayout, mAlternatesSelectedLayout);
939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setLeft(mLeft);
940c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setRight(mLeft);
941e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setAnchorView(mAnchorView);
942e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setAdapter(mAlternatesAdapter);
943e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setWidth(getWidth());
944e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setOnItemClickListener(this);
945e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.show();
946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
947c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void setSelected(boolean selected) {
950c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelected = selected;
951c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
952c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
953c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getDisplay() {
954c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDisplay;
955c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getValue() {
958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mValue;
959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean isInDelete(int offset, float x, float y) {
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Figure out the bounds of this chip and whether or not
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // the user clicked in the X portion.
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mSelected
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    && (offset == getChipEnd()
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                            || (x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right));
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public boolean matchesChip(int offset) {
970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = getChipStart();
971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getChipEnd();
972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return (offset >= start && offset <= end);
973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onClick(View widget, int offset, float x, float y) {
976c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mSelected) {
977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (isInDelete(offset, x, y)) {
978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    removeChip();
9791a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                } else {
9801a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                    clearSelectedChip();
981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
982c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
983c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
984c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
985c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
986c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
987c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                int y, int bottom, Paint paint) {
988c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Shift the bounds of this span to where it is actually drawn on the screeen.
989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mLeft = (int) x;
990c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
991c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
992c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
993c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
994c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) {
995e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            mAlternatesPopup.dismiss();
996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
997c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replaceChip(mAlternatesAdapter.getRecipientEntry(position));
998c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
999c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1000c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getContactId() {
1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mContactId;
1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1004c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getDataId() {
1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDataId;
1006c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
10072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
10082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira}
1009c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1010