RecipientEditTextView.java revision 4f7412c084ad344e94a320b51270ac6480a47a84
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/*
22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project
32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License.
62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at
72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software
112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and
142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License.
152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips;
182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context;
20c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap;
211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory;
22c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix;
24c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Paint;
25c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
30c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
33c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
34c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
35c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
36c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
37c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
38c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode.Callback;
48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereiraimport android.widget.AutoCompleteTextView.Validator;
51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
53b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
54b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
56c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
57c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
58c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.ArrayList;
590f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView
654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    implements OnItemClickListener, Callback {
66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private static final int INVALID_CONTACT = -1;
738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct size from UX.
754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final float MORE_WIDTH_FACTOR = 0.25f;
764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
77c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
78c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
79c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
86c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
87c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
88c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
89c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipDeleteWidth;
90c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesSelectedLayout;
940f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private int mMoreString;
1004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
103e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
104e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
105e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
106e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
109045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
110045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
1112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
113d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        setSuggestionsEnabled(false);
114c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
1154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
116c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // When the user starts typing, make sure we unselect any selected
117c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // chips.
118c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        addTextChangedListener(new TextWatcher() {
119c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
120c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void afterTextChanged(Editable s) {
121c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
122c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
123c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
124c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void onTextChanged(CharSequence s, int start, int before, int count) {
125c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
126c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
127c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
128c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
129fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira                // TODO: find a better way to unfocus a chip when a user starts typing.
130c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
131c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        });
1324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
1334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
1354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
1364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
1374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
13883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
13983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
14083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
14183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
1424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
1434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
144fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira                setSelection(Math.min(chips[chips.length - 1].getChipEnd() + 1, textLength));
1454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
146d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
147d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
148d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
149d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
150d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
151d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
152d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
1534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
1544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
1554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
1564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
157d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
1582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
1592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
1614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mSelectedChip != null) {
1624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
1634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
1644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            commitDefault();
1654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        mMoreChip = createMoreChip();
1674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
1684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
1704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
1714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
1724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
1734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
1744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
1754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
177e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
1781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth, TextUtils.TruncateAt.END);
1791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
181e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
1821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
1831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
1841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
185e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
1861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
1871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
1881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
1891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
1911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
1921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
1931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
1941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
195c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
1971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
1991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
2001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
2011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
2021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
2041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
205e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    - Math.abs(height - mChipFontSize)/2, paint);
2061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
2071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
2081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
2091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
2101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
2111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
2121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
2131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
215045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
216045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
217045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
218045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
219045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
220045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        return mValidator != null && mValidator.isValid(contact.getDestination()) ?
221045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
222045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
223045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
224e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
225c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
226c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
228e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
2291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
2301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
2311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
2321a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
2331a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
2341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
2351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
2361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
237c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
241045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
242045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
243045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
244045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
2451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
2478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            if (contact.getContactId() != INVALID_CONTACT) {
2489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
24990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
25090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
25190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
25290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
25390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
25490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
25590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
25690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
25790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
2589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
2599024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
2609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
2619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
2629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
2639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
2649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
2659024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
2669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
2679024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
2689024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF dst = new RectF(0, 0, iconWidth, height);
2699024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
2709024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
2729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
2739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
274c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
2751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
2771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding + iconWidth,
27890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    height - Math.abs(height - mChipFontSize) / 2, paint);
279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
2801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
2811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
2821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
2831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
2851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
2861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
2871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
2881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
2891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
2911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
2921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int line = layout.getLineForOffset(offset);
2931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int lineTop = layout.getLineTop(line);
2941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
2951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
2961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
2981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
299e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
302045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
3031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Get the location of the widget so we can properly offset
306c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // the anchor for each chip.
307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int[] xy = new int[2];
308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        getLocationOnScreen(xy);
309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
3111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
3121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + tmpBitmap.getWidth(),
313e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                calculateLineBottom(xy[1], line, tmpBitmap.getHeight()));
314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds);
315045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        recipientChip
316045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                .setIsValid(mValidator != null && mValidator.isValid(contact.getDestination()));
317c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
3212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
3238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
324045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
325045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
3268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * 2) the height of a line in the autocomplete view vs the heigt of a chip
3278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * 3) padding built into the edit text view will move the bottom position
3288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * 4) the position of the autocomplete view on the screen, taking into account
3298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that any top padding will move this down visually
3308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
331e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private int calculateLineBottom(int yOffset, int line, int chipHeight) {
332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int bottomPadding = 0;
333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (line == getLineCount() - 1) {
334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            bottomPadding += getPaddingBottom();
335c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
336e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        return ((line + 1) * getLineHeight()) + yOffset + getPaddingTop()
337e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                + (chipHeight - getLineHeight()) + bottomPadding;
338f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
339f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
3408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
3418684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
3428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
3438684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
3448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
345c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
3461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
3472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
3504f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
3514f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
3524f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
3544f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesSelectedLayout
359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
36143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
362045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
363045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            int alternatesLayout, int alternatesSelectedLayout, float chipHeight, float padding,
364045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            float chipFontSize) {
365045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mChipBackground = chipBackground;
366045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
367045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mChipDelete = chipDelete;
368045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mChipPadding = (int) padding;
369045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mAlternatesLayout = alternatesLayout;
370045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mAlternatesSelectedLayout = alternatesSelectedLayout;
371045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mDefaultContactPhoto = defaultContact;
372045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mMoreString = moreResource;
373045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mChipHeight = chipHeight;
374045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mChipFontSize = chipFontSize;
375045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mInvalidChipBackground = invalidChip;
376045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
377045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
378c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
380c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
382c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
3858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
3868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
3878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
3888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
3898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
3908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
3918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
3928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
3938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
3948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
4028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
40495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
40595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
40695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
40795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
40895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
40995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
41095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
4118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
4138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
4148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
4158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
4168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
4178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
4188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
41995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
423c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_TAB:
425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
426d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
43095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
431c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
435045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
436045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
437045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
438045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
439045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
440045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
4428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
4434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
4444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        boolean enough = enoughToFilter();
4454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        boolean shouldSubmitAtPosition = false;
446045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
4474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
4484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (enough) {
4494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
4504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if ((chips == null || chips.length == 0)) {
4514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // There's something being filtered or typed that has not been
4524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // completed yet.
4534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                shouldSubmitAtPosition = true;
4544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
4554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
4564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
4574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (shouldSubmitAtPosition) {
4584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (getAdapter().getCount() > 0) {
4594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // choose the first entry.
4604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                submitItemAtPosition(0);
461d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
4624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
4634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            } else {
4644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                String text = editable.toString().substring(start, end);
4654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                clearComposingText();
46690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (text != null && text.length() > 0 && !text.equals(" ")) {
4674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
4684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
469fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    editable.replace(start, end, createChip(entry, false));
4704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    dismissDropDown();
4714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
4724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return false;
473d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
474d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
4754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
476d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
477d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
4788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
4808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
4818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
482c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
483c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
485c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.onKeyDown(keyCode, event);
486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
489c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
490c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
492c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
4932d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
495c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return (Spannable) getText();
497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
522c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
523c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.unselectChip();
525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
526c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
52736d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
528c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
5328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
5338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
5348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
5358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
5368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
5378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
538c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
540d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
541d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
542d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
543d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
544d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
546d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
552c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
556c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
5608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // Selection may have moved due to the tap event,
5618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // but make sure we correctly reset selection to the
5628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // end so that any unfinished chips are committed.
5638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
5648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
565c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip.onClick(this, offset, x, y);
568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
573c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
576c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
583c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
588c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
594c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5964fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
5974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
5984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
600c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
6014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
602c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
603c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
604c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
605c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
606c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
607c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
608c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
6094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
6104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
6114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
6124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
6134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
6144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
6154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
616c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
617c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
618c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
619c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
620c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chip.matchesChip(offset)) {
622c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
623c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
624c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
625c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
626c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
627c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
628fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
629c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        CharSequence displayText = mTokenizer.terminateToken(entry.getDestination());
630c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
631fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        int textLength = displayText.length()-1;
632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
633c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
634c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
635c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
636fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            chipText.setSpan(constructChipSpan(entry, start, pressed), 0, textLength,
637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
638c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
639c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
640c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
642c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
643c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
645c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
6468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
6478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
6488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
6498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
650c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
651c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
652c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
653c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
654c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
656c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position);
6579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        // If the display name and the address are the same, then make this
6589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        // a fake recipient that is editable.
6599024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        if (TextUtils.equals(entry.getDisplayName(), entry.getDestination())) {
6609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            entry = RecipientEntry.constructFakeEntry(entry.getDestination());
6619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
662c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
663c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
664c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
665c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
666c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
667c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
668c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
669fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        editable.replace(start, end, createChip(entry, false));
670c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
671c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
672c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
673c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
674c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
6757a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
6767a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
6777a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
6787a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
6797a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
680c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
681c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
682c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
683c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
68483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
68583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
68683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
68783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
688c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
689c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
690c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
69183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
6927a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
6937a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
6947a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
6957a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
696c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
697c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
698c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
699c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
7024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
7034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
7044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
7074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
7084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
7114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
7124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
7134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
7178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
7184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
7194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
7204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
7214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
724045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
725045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
726045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
7278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
7284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan createMoreChip() {
72983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] recipients = getRecipients();
73083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
7314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            return null;
7324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
73383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
7344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
7354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
7364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // TODO: get the correct size from visual design.
7374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int width = (int) Math.floor(getWidth() * MORE_WIDTH_FACTOR);
7384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
7394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
7404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
7414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        String moreText = getResources().getString(mMoreString, overage);
7424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
7434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                getPaint());
7444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
7464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
7474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        ImageSpan moreSpan = new ImageSpan(result);
7484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Spannable spannable = getSpannable();
7494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
7504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        RecipientChip[] chips = spannable.getSpans(0, text.length(), RecipientChip.class);
7514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (chips == null || chips.length == 0) {
7524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
7534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                "We have recipients. Tt should not be possible to have zero RecipientChips.");
7544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            return null;
7554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
756045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>(chips.length);
7574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
7584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
7594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        for (int i = numRecipients - overage; i < chips.length; i++) {
7604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mRemovedSpans.add(chips[i]);
7619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
7629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                totalReplaceStart = chips[i].getChipStart();
7639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
7649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == chips.length - 1) {
7659024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                totalReplaceEnd = chips[i].getChipEnd();
7669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
7679024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            chips[i].setPreviousChipStart(chips[i].getChipStart());
7689024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            chips[i].setPreviousChipEnd(chips[i].getChipEnd());
7694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            spannable.removeSpan(chips[i]);
7704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
7714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(totalReplaceStart,
7724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                totalReplaceEnd));
7734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
7744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        text.replace(totalReplaceStart, totalReplaceEnd, chipText);
7754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return moreSpan;
7764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
7808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
7818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
7824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
7834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
7844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
7854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
7864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
7874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
7884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
7894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
7904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
7914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                SpannableString associatedText;
7924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
7939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    int chipStart = chip.getPreviousChipStart();
7949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    int chipEnd = chip.getPreviousChipEnd();
7954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    associatedText = new SpannableString(editable.subSequence(chipStart, chipEnd));
7964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    associatedText.setSpan(chip, 0, associatedText.length(),
7974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
7984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    editable.replace(chipStart, chipEnd, associatedText);
7994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
8004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
8014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
8024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
8034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
8044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * RecipientChip defines an ImageSpan that contains information relevant to
807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * a particular recipient.
808c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
809c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira    public class RecipientChip extends ImageSpan implements OnItemClickListener {
810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mDisplay;
811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
812c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mValue;
813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private View mAnchorView;
815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private int mLeft;
817c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
818c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mContactId;
819c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
820c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mDataId;
821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
822c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientEntry mEntry;
823c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
824c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean mSelected = false;
825c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientAlternatesAdapter mAlternatesAdapter;
827c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private Rect mBounds;
829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        private int mStart = -1;
83183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
8324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        private int mEnd = -1;
8334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
834fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        private ListPopupWindow mAlternatesPopup;
8358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        private boolean mValid = true;
8378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
838c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) {
839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super(drawable);
840c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDisplay = entry.getDisplayName();
841c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mValue = entry.getDestination();
842c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mContactId = entry.getContactId();
843c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDataId = entry.getDataId();
844c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mEntry = entry;
845c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mBounds = bounds;
846c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
847c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView = new View(getContext());
848c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setLeft(bounds.left);
849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setRight(bounds.left);
850c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setTop(bounds.bottom);
851c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setBottom(bounds.bottom);
852c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setVisibility(View.GONE);
853c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        public void setIsValid(boolean isValid) {
8568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            mValid = isValid;
8579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
8589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
8598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Store the offset in the spannable where this RecipientChip
8618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * is currently being displayed.
8628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8639024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public void setPreviousChipStart(int start) {
8649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            mStart = start;
8659024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
8669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
8678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the offset in the spannable where this RecipientChip
8698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * was currently being displayed. Use this to determine where
8708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * to place a RecipientChip that has been hidden when the
8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * RecipientEditTextView loses focus.
8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        public int getPreviousChipStart() {
8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            return mStart;
8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        }
8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Store the end offset in the spannable where this RecipientChip
8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * is currently being displayed.
8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public void setPreviousChipEnd(int end) {
8829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            mEnd = end;
8839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
8849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the end offset in the spannable where this RecipientChip
8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * was currently being displayed. Use this to determine where
8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * to place a RecipientChip that has been hidden when the
8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * RecipientEditTextView loses focus.
8908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        public int getPreviousChipEnd() {
8928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            return mEnd;
8938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        }
8948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
896045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * Remove selection from this chip. Unselecting a RecipientChip will render
897045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * the chip without a delete icon and with an unfocused background. This
898045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * is called when the RecipientChip not longer has focus.
8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
900c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void unselectChip() {
901fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int start = getChipStart();
902fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int end = getChipEnd();
903fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
90483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
90583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                Log.e(TAG, "The chip being unselected no longer exists but should.");
90683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
90783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                getSpannable().removeSpan(this);
90883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                QwertyKeyListener.markAsReplaced(editable, start, end, "");
90983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                editable.replace(start, end, createChip(mEntry, false));
91083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
911fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            mSelectedChip = null;
91283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            clearSelectedChip();
913fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            setCursorVisible(true);
914fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            setSelection(editable.length());
915fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
916fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
9178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
918045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * Show this chip as selected. If the RecipientChip is just an email address,
9198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * selecting the chip will take the contents of the chip and place it at
9208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * the end of the RecipientEditTextView for inline editing. If the
9218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * RecipientChip is a complete contact, then selecting the chip
9228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * will change the background color of the chip, show the delete icon,
9238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * and a popup window with the address in use highlighted and any other
9248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * alternate addresses for the contact.
9258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * @return A RecipientChip in the selected state or null if the chip
9268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * just contained an email address.
9278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
928fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        public RecipientChip selectChip() {
9298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            if (mEntry.getContactId() != INVALID_CONTACT) {
930fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                int start = getChipStart();
931fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                int end = getChipEnd();
932fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                getSpannable().removeSpan(this);
933fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                RecipientChip newChip;
934fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                CharSequence displayText = mTokenizer.terminateToken(mEntry.getDestination());
935fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                // Always leave a blank space at the end of a chip.
93683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                int textLength = displayText.length() - 1;
937fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                SpannableString chipText = new SpannableString(displayText);
938fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                try {
939fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    newChip = constructChipSpan(mEntry, start, true);
940fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    chipText.setSpan(newChip, 0, textLength,
941fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
942fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                } catch (NullPointerException e) {
943fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    Log.e(TAG, e.getMessage(), e);
944fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    return null;
945fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                }
946fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                Editable editable = getText();
947fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                QwertyKeyListener.markAsReplaced(editable, start, end, "");
94883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                if (start == -1 || end == -1) {
94983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                    Log.d(TAG, "The chip being selected no longer exists but should.");
95083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                } else {
95183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                    editable.replace(start, end, chipText);
95283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                }
953fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setCursorVisible(false);
954fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                newChip.setSelected(true);
955fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                newChip.showAlternates();
956fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setCursorVisible(false);
957fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                return newChip;
958fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            } else {
959fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                CharSequence text = getValue();
960fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                Editable editable = getText();
961fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                removeChip();
962fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                editable.append(text);
963fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setCursorVisible(true);
964fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setSelection(editable.length());
965fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                return null;
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
970045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * Handle key events for a chip. When the keyCode equals
971045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * KeyEvent.KEYCODE_DEL, this deletes the currently selected chip.
9728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onKeyDown(int keyCode, KeyEvent event) {
974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (keyCode == KeyEvent.KEYCODE_DEL) {
975e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
976e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    mAlternatesPopup.dismiss();
977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                removeChip();
979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
980c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
9838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Remove this chip and any text associated with it from the RecipientEditTextView.
9848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
9858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        private void removeChip() {
986c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable spannable = getSpannable();
9879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            int spanStart = spannable.getSpanStart(this);
9889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            int spanEnd = spannable.getSpanEnd(this);
989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Editable text = getText();
990c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int toDelete = spanEnd;
9919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            boolean wasSelected = this == mSelectedChip;
9929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Clear that there is a selected chip before updating any text.
9939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
9949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                mSelectedChip = null;
9959024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Always remove trailing spaces when removing a chip.
99755bb2833b29945c08b809408ff94ddf7703e911aMindy Pereira            while (toDelete >= 0 && toDelete < text.length() - 1 && text.charAt(toDelete) == ' ') {
998c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                toDelete++;
999c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1000c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.removeSpan(this);
1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            text.delete(spanStart, toDelete);
10029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
100336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira                clearSelectedChip();
100436d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira            }
1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1006c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the start offset of this chip in the view.
10098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipStart() {
10119024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return getSpannable().getSpanStart(this);
1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the end offset of this chip in the view.
10168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipEnd() {
10189024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return getSpannable().getSpanEnd(this);
1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Replace this currently selected chip with a new chip
10238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * that uses the contact data provided.
10248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1025c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void replaceChip(RecipientEntry entry) {
1026fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            boolean wasSelected = this == mSelectedChip;
1027fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            if (wasSelected) {
1028fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                mSelectedChip = null;
1029c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1030fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int start = getSpannable().getSpanStart(this);
1031fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int end = getSpannable().getSpanEnd(this);
1032fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            getSpannable().removeSpan(this);
1033fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1034fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            CharSequence chipText = createChip(entry, false);
1035fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            if (start == -1 || end == -1) {
1036fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1037fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                editable.insert(0, chipText);
1038c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1039fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                editable.replace(start, end, chipText);
1040fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            }
1041fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            setCursorVisible(true);
1042fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            if (wasSelected) {
1043fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                clearSelectedChip();
1044c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1045c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1046c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Show all addresses associated with a contact.
10498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1050c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void showAlternates() {
1051fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            mAlternatesPopup = new ListPopupWindow(getContext());
1052c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1053e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            if (!mAlternatesPopup.isShowing()) {
1054c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAlternatesAdapter = new RecipientAlternatesAdapter(
1055e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                        getContext(),
1056c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mEntry.getContactId(), mEntry.getDataId(),
1057c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mAlternatesLayout, mAlternatesSelectedLayout);
1058c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setLeft(mLeft);
1059c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setRight(mLeft);
1060e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setAnchorView(mAnchorView);
1061e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setAdapter(mAlternatesAdapter);
1062e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setWidth(getWidth());
1063e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setOnItemClickListener(this);
1064e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.show();
1065c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1066c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1067c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1068c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void setSelected(boolean selected) {
1069c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelected = selected;
1070c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1071c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the text displayed in the chip.
10748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1075c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getDisplay() {
1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDisplay;
1077c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the text value this chip represents.
10818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getValue() {
1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mValue;
1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * See if a touch event was inside the delete target of
10888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * a selected chip. It is in the delete target if:
10898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * 1) the x and y points of the event are within the
10908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * delete assset.
10918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * 2) the point tapped would have caused a cursor to appear
10928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * right after the selected chip.
10938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean isInDelete(int offset, float x, float y) {
1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Figure out the bounds of this chip and whether or not
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // the user clicked in the X portion.
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mSelected
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    && (offset == getChipEnd()
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                            || (x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right));
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
11038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Return whether this chip contains the position passed in.
11048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public boolean matchesChip(int offset) {
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = getChipStart();
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getChipEnd();
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return (offset >= start && offset <= end);
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
1112045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * Handle click events for a chip. When a selected chip receives a click
1113045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * event, see if that event was in the delete icon. If so, delete it.
1114045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * Otherwise, unselect the chip.
11158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onClick(View widget, int offset, float x, float y) {
1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mSelected) {
1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (isInDelete(offset, x, y)) {
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    removeChip();
11201a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                } else {
11211a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                    clearSelectedChip();
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                int y, int bottom, Paint paint) {
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Shift the bounds of this span to where it is actually drawn on the screeen.
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mLeft = (int) x;
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
1135045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira         * Handle clicks to alternate addresses for a selected chip. If the user
11368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * selects an alternate, the chip is replaced with a new contact with the
11378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * new contact address information.
11388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) {
1141e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            mAlternatesPopup.dismiss();
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replaceChip(mAlternatesAdapter.getRecipientEntry(position));
1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
11478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the id of the contact associated with this chip.
11488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getContactId() {
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mContactId;
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
11548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the id of the data associated with this chip.
11558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getDataId() {
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDataId;
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
11592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira}
1161