RecipientEditTextView.java revision 8684974e4befb4c9dcc21c995c4ff3af7103ab10
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
1092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
111d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        setSuggestionsEnabled(false);
112c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
1134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
114c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // When the user starts typing, make sure we unselect any selected
115c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // chips.
116c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        addTextChangedListener(new TextWatcher() {
117c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
118c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void afterTextChanged(Editable s) {
119c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
120c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
121c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
122c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void onTextChanged(CharSequence s, int start, int before, int count) {
123c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
124c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
125c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
126c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
127fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira                // TODO: find a better way to unfocus a chip when a user starts typing.
128c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
129c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        });
1304fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
1314fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
1334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
1344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
1354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
13683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
13783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
13883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
13983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
1404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
1414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
142fcdaf46366afd32e8b35406216dbb3c6961793aaMindy Pereira                setSelection(Math.min(chips[chips.length - 1].getChipEnd() + 1, textLength));
1434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
144d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
145d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
146d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
147d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
148d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
149d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
150d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
1514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
1524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
1534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
1544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
155d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
1562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
1572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
1594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mSelectedChip != null) {
1604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
1614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
1624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            commitDefault();
1634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        mMoreChip = createMoreChip();
1654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
1664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
1684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
1694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
1704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
1714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
1724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
1734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
175e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
1761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth, TextUtils.TruncateAt.END);
1771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
179e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
1801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
1811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
1821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
183e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
1841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
1851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
1861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
1871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
1891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
1901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
1911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
1921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
1951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
1971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
1981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
1991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
2001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
2021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
203e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    - Math.abs(height - mChipFontSize)/2, paint);
2041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
2051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
2061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
2071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
2081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
2091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
2101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
2111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
213e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
217e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
2181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
2191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
2201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
2211a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
2221a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
2231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
2241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
2251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
226c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
228c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
229c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
2301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground != null) {
2311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackground.setBounds(0, 0, width, height);
2321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackground.draw(canvas);
2331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2349024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
2358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            if (contact.getContactId() != INVALID_CONTACT) {
2369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
23790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
23890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
23990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
24090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
24190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
24290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
24390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
24490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
24590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
2469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
2479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
2489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
2499024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
2509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
2519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
2529024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
2539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
2549024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
2559024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
2569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF dst = new RectF(0, 0, iconWidth, height);
2579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
2589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
2609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
2619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
2631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
2651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding + iconWidth,
26690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    height - Math.abs(height - mChipFontSize) / 2, paint);
267c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
2681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
2691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
2701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
2711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
272c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
2731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
2741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
2751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
2761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
2771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
278c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
2791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
2801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int line = layout.getLineForOffset(offset);
2811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int lineTop = layout.getLineTop(line);
2828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        boolean isValid = mValidator != null && mValidator.isValid(contact.getDestination());
2831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
2841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
2851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
2861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
2871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
288e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
2901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
2918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            if (!isValid) {
2928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
2938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            } else {
2948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                tmpBitmap = createUnselectedChip(contact, paint, layout);
2958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            }
2961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
297c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
298c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Get the location of the widget so we can properly offset
299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // the anchor for each chip.
300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int[] xy = new int[2];
301c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        getLocationOnScreen(xy);
302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
3041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
3051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + tmpBitmap.getWidth(),
306e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                calculateLineBottom(xy[1], line, tmpBitmap.getHeight()));
307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds);
3088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        recipientChip.setIsValid(isValid);
309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
311c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
3132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
3158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
3168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * The bottom of the line the chip will be located on is calculated by 4
3178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * factors:
3188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * 1) which line the chip appears on
3198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * 2) the height of a line in the autocomplete view vs the heigt of a chip
3208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * 3) padding built into the edit text view will move the bottom position
3218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * 4) the position of the autocomplete view on the screen, taking into account
3228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that any top padding will move this down visually
3238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
324e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private int calculateLineBottom(int yOffset, int line, int chipHeight) {
325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int bottomPadding = 0;
326c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (line == getLineCount() - 1) {
327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            bottomPadding += getPaddingBottom();
328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
329e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        return ((line + 1) * getLineHeight()) + yOffset + getPaddingTop()
330e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                + (chipHeight - getLineHeight()) + bottomPadding;
331f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
332f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
3338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
3348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
3358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
3368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
3378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
338c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
3391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
3402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3412d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
342c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
343c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Set all chip dimensions and resources. This has to be done from the application
344c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * as this is a static library.
345c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param chipBackground drawable
3461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
3471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
3481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
3491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesSelectedLayout
351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
35343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
3544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Drawable chipDelete, Bitmap defaultContact, int moreResource, int alternatesLayout,
355e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            int alternatesSelectedLayout, float chipHeight, float padding, float chipFontSize) {
356c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipBackground = chipBackground;
357c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipDelete = chipDelete;
359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipPadding = (int) padding;
360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mAlternatesLayout = alternatesLayout;
361c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mAlternatesSelectedLayout = alternatesSelectedLayout;
3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        mDefaultContactPhoto = defaultContact;
3634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        mMoreString = moreResource;
364e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        mChipHeight = chipHeight;
365e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        mChipFontSize = chipFontSize;
366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
369c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
370c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
371c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
372c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
373c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
3758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
3768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
3778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
3788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
3798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
3808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
3818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
3828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
3838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
3848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
385c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
386c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
3918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
3928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
39495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
39595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
39695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
39795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
39895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
39995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
40095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
4018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
4038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
4048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
4058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
4068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
4078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
4088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
40995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_TAB:
415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
416d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
419c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
42095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
423c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
4268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        return commitDefault(getSelectionEnd());
4278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
4288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
429d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    // If the popup is showing, the default is the first item in the popup
430d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    // suggestions list. Otherwise, it is whatever the user had typed in.
4318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    // End represents where the the tokenizer should search for a token
4328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    // to turn into a chip.
4338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault(int end) {
4344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
4354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        boolean enough = enoughToFilter();
4364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        boolean shouldSubmitAtPosition = false;
4378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
4384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
4394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (enough) {
4404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
4414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if ((chips == null || chips.length == 0)) {
4424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // There's something being filtered or typed that has not been
4434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // completed yet.
4444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                shouldSubmitAtPosition = true;
4454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
4464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
4474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
4484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (shouldSubmitAtPosition) {
4494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (getAdapter().getCount() > 0) {
4504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // choose the first entry.
4514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                submitItemAtPosition(0);
452d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
4534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
4544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            } else {
4554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                String text = editable.toString().substring(start, end);
4564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                clearComposingText();
45790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (text != null && text.length() > 0 && !text.equals(" ")) {
4584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
4594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
460fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    editable.replace(start, end, createChip(entry, false));
4614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    dismissDropDown();
4624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
4634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return false;
464d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
465d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
4664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
467d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
468d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
4698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
4718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
4728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
473c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
476c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.onKeyDown(keyCode, event);
477c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
480c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
481c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
482c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
483c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
4842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return (Spannable) getText();
488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
489c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
490c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
492c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
493c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
494c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
495c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.unselectChip();
516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
51836d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
5238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
5248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
5258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
5268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
5278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
5288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
531d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
532d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
533d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
534d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
535d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
536c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
537d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
538c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
541c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
542c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
543c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
5518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // Selection may have moved due to the tap event,
5528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // but make sure we correctly reset selection to the
5538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // end so that any unfinished chips are committed.
5548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
5558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
556c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip.onClick(this, offset, x, y);
559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
560c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
563c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
565c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
573c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
576c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
583c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5874fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
5884fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
5894fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5924fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
593c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
594c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
596c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
597c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
598c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
6004fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
6014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
6024fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
6034fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
6044fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
6054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
6064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
607c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
608c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
609c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
610c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
611c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
612c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chip.matchesChip(offset)) {
613c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
614c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
615c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
616c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
617c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
618c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
619fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
620c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        CharSequence displayText = mTokenizer.terminateToken(entry.getDestination());
621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
622fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        int textLength = displayText.length()-1;
623c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
624c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
625c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
626c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
627fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            chipText.setSpan(constructChipSpan(entry, start, pressed), 0, textLength,
628c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
629c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
630c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
631c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
633c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
634c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
635c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
636c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
6378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
6388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
6398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
6408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
642c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
643c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
645c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
646c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
647c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position);
6489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        // If the display name and the address are the same, then make this
6499024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        // a fake recipient that is editable.
6509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        if (TextUtils.equals(entry.getDisplayName(), entry.getDestination())) {
6519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            entry = RecipientEntry.constructFakeEntry(entry.getDestination());
6529024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
653c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
654c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
656c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
657c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
658c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
659c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
660fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        editable.replace(start, end, createChip(entry, false));
661c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
662c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
663c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
664c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
665c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
6667a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
6677a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
6687a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
6697a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
6707a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
671c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
672c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
673c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
674c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
67583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
67683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
67783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
67883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
679c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
680c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
681c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
68283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
6837a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
6847a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
6857a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
6867a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
687c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
688c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
689c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
690c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
6914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
6934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
6944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
6954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
6964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
6974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
6984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
6994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
7024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
7034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
7044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
7088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
7094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
7104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
7114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
7124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * The more chip is text that replaces any chips that do not fit in the pre-defined
7168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * available space when the RecipientEditTextView loses focus.
7178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
7184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan createMoreChip() {
71983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] recipients = getRecipients();
72083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
7214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            return null;
7224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
72383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
7244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
7254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
7264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // TODO: get the correct size from visual design.
7274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int width = (int) Math.floor(getWidth() * MORE_WIDTH_FACTOR);
7284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
7294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
7304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
7314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        String moreText = getResources().getString(mMoreString, overage);
7324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
7334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                getPaint());
7344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
7364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
7374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        ImageSpan moreSpan = new ImageSpan(result);
7384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Spannable spannable = getSpannable();
7394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
7404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        RecipientChip[] chips = spannable.getSpans(0, text.length(), RecipientChip.class);
7414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (chips == null || chips.length == 0) {
7424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
7434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                "We have recipients. Tt should not be possible to have zero RecipientChips.");
7444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            return null;
7454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
7464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
7474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
7484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
7494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        for (int i = numRecipients - overage; i < chips.length; i++) {
7504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mRemovedSpans.add(chips[i]);
7519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
7529024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                totalReplaceStart = chips[i].getChipStart();
7539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
7549024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == chips.length - 1) {
7559024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                totalReplaceEnd = chips[i].getChipEnd();
7569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
7579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            chips[i].setPreviousChipStart(chips[i].getChipStart());
7589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            chips[i].setPreviousChipEnd(chips[i].getChipEnd());
7594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            spannable.removeSpan(chips[i]);
7604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
7614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(totalReplaceStart,
7624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                totalReplaceEnd));
7634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
7644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        text.replace(totalReplaceStart, totalReplaceEnd, chipText);
7654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return moreSpan;
7664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
7688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
7708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
7718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
7724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
7734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
7744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
7754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
7764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
7774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
7784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
7794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
7804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
7814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                SpannableString associatedText;
7824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
7839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    int chipStart = chip.getPreviousChipStart();
7849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    int chipEnd = chip.getPreviousChipEnd();
7854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    associatedText = new SpannableString(editable.subSequence(chipStart, chipEnd));
7864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    associatedText.setSpan(chip, 0, associatedText.length(),
7874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
7884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                    editable.replace(chipStart, chipEnd, associatedText);
7894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
7904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
7914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
7924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
7934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
7944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
795c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * RecipientChip defines an ImageSpan that contains information relevant to
797c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * a particular recipient.
798c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
799c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira    public class RecipientChip extends ImageSpan implements OnItemClickListener {
800c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mDisplay;
801c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
802c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mValue;
803c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
804c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private View mAnchorView;
805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private int mLeft;
807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
808c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mContactId;
809c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mDataId;
811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
812c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientEntry mEntry;
813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean mSelected = false;
815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientAlternatesAdapter mAlternatesAdapter;
817c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
818c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private Rect mBounds;
819c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        private int mStart = -1;
82183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
8224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        private int mEnd = -1;
8234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
824fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        private ListPopupWindow mAlternatesPopup;
8258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        private boolean mValid = true;
8278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) {
829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super(drawable);
830c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDisplay = entry.getDisplayName();
831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mValue = entry.getDestination();
832c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mContactId = entry.getContactId();
833c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDataId = entry.getDataId();
834c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mEntry = entry;
835c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mBounds = bounds;
836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
837c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView = new View(getContext());
838c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setLeft(bounds.left);
839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setRight(bounds.left);
840c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setTop(bounds.bottom);
841c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setBottom(bounds.bottom);
842c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setVisibility(View.GONE);
843c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
844c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        public void setIsValid(boolean isValid) {
8468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            mValid = isValid;
8479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
8489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
8498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Store the offset in the spannable where this RecipientChip
8518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * is currently being displayed.
8528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public void setPreviousChipStart(int start) {
8549024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            mStart = start;
8559024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
8569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
8578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the offset in the spannable where this RecipientChip
8598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * was currently being displayed. Use this to determine where
8608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * to place a RecipientChip that has been hidden when the
8618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * RecipientEditTextView loses focus.
8628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        public int getPreviousChipStart() {
8648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            return mStart;
8658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        }
8668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Store the end offset in the spannable where this RecipientChip
8698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * is currently being displayed.
8708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8719024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        public void setPreviousChipEnd(int end) {
8729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            mEnd = end;
8739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira        }
8749024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira
8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the end offset in the spannable where this RecipientChip
8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * was currently being displayed. Use this to determine where
8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * to place a RecipientChip that has been hidden when the
8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * RecipientEditTextView loses focus.
8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        public int getPreviousChipEnd() {
8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            return mEnd;
8838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        }
8848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
8868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Unselecting a RecipientChip will render the chip without a delete icon
8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * and with an unfocused background. This is called when the RecipientChip
8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * not longer has focus.
8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void unselectChip() {
891fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int start = getChipStart();
892fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int end = getChipEnd();
893fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
89483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
89583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                Log.e(TAG, "The chip being unselected no longer exists but should.");
89683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
89783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                getSpannable().removeSpan(this);
89883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                QwertyKeyListener.markAsReplaced(editable, start, end, "");
89983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                editable.replace(start, end, createChip(mEntry, false));
90083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
901fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            mSelectedChip = null;
90283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            clearSelectedChip();
903fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            setCursorVisible(true);
904fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            setSelection(editable.length());
905fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
906fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * If the RecipientChip is just an email address,
9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * selecting the chip will take the contents of the chip and place it at
9108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * the end of the RecipientEditTextView for inline editing. If the
9118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * RecipientChip is a complete contact, then selecting the chip
9128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * will change the background color of the chip, show the delete icon,
9138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * and a popup window with the address in use highlighted and any other
9148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * alternate addresses for the contact.
9158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * @return A RecipientChip in the selected state or null if the chip
9168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * just contained an email address.
9178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
918fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        public RecipientChip selectChip() {
9198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira            if (mEntry.getContactId() != INVALID_CONTACT) {
920fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                int start = getChipStart();
921fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                int end = getChipEnd();
922fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                getSpannable().removeSpan(this);
923fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                RecipientChip newChip;
924fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                CharSequence displayText = mTokenizer.terminateToken(mEntry.getDestination());
925fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                // Always leave a blank space at the end of a chip.
92683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                int textLength = displayText.length() - 1;
927fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                SpannableString chipText = new SpannableString(displayText);
928fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                try {
929fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    newChip = constructChipSpan(mEntry, start, true);
930fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    chipText.setSpan(newChip, 0, textLength,
931fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
932fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                } catch (NullPointerException e) {
933fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    Log.e(TAG, e.getMessage(), e);
934fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                    return null;
935fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                }
936fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                Editable editable = getText();
937fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                QwertyKeyListener.markAsReplaced(editable, start, end, "");
93883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                if (start == -1 || end == -1) {
93983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                    Log.d(TAG, "The chip being selected no longer exists but should.");
94083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                } else {
94183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                    editable.replace(start, end, chipText);
94283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira                }
943fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setCursorVisible(false);
944fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                newChip.setSelected(true);
945fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                newChip.showAlternates();
946fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setCursorVisible(false);
947fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                return newChip;
948fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            } else {
949fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                CharSequence text = getValue();
950fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                Editable editable = getText();
951fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                removeChip();
952fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                editable.append(text);
953fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setCursorVisible(true);
954fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                setSelection(editable.length());
955fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                return null;
956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
9608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * If keyCode equals KeyEvent.KEYCODE_DEL, this deletes the currently
9618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * selected chip.
9628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onKeyDown(int keyCode, KeyEvent event) {
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (keyCode == KeyEvent.KEYCODE_DEL) {
965e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
966e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    mAlternatesPopup.dismiss();
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                removeChip();
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
9738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Remove this chip and any text associated with it from the RecipientEditTextView.
9748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
9758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        private void removeChip() {
976c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable spannable = getSpannable();
9779024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            int spanStart = spannable.getSpanStart(this);
9789024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            int spanEnd = spannable.getSpanEnd(this);
979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Editable text = getText();
980c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int toDelete = spanEnd;
9819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            boolean wasSelected = this == mSelectedChip;
9829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Clear that there is a selected chip before updating any text.
9839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
9849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                mSelectedChip = null;
9859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
986c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Always remove trailing spaces when removing a chip.
98755bb2833b29945c08b809408ff94ddf7703e911aMindy Pereira            while (toDelete >= 0 && toDelete < text.length() - 1 && text.charAt(toDelete) == ' ') {
988c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                toDelete++;
989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
990c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.removeSpan(this);
991c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            text.delete(spanStart, toDelete);
9929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (wasSelected) {
99336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira                clearSelectedChip();
99436d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira            }
995c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
9988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the start offset of this chip in the view.
9998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1000c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipStart() {
10019024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return getSpannable().getSpanStart(this);
1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the end offset of this chip in the view.
10068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1007c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipEnd() {
10089024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            return getSpannable().getSpanEnd(this);
1009c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Replace this currently selected chip with a new chip
10138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * that uses the contact data provided.
10148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void replaceChip(RecipientEntry entry) {
1016fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            boolean wasSelected = this == mSelectedChip;
1017fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            if (wasSelected) {
1018fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                mSelectedChip = null;
1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1020fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int start = getSpannable().getSpanStart(this);
1021fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            int end = getSpannable().getSpanEnd(this);
1022fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            getSpannable().removeSpan(this);
1023fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1024fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            CharSequence chipText = createChip(entry, false);
1025fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            if (start == -1 || end == -1) {
1026fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1027fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                editable.insert(0, chipText);
1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1029fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                editable.replace(start, end, chipText);
1030fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            }
1031fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            setCursorVisible(true);
1032fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            if (wasSelected) {
1033fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira                clearSelectedChip();
1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1036c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Show all addresses associated with a contact.
10398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1040c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void showAlternates() {
1041fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            mAlternatesPopup = new ListPopupWindow(getContext());
1042c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1043e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            if (!mAlternatesPopup.isShowing()) {
1044c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAlternatesAdapter = new RecipientAlternatesAdapter(
1045e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                        getContext(),
1046c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mEntry.getContactId(), mEntry.getDataId(),
1047c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mAlternatesLayout, mAlternatesSelectedLayout);
1048c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setLeft(mLeft);
1049c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setRight(mLeft);
1050e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setAnchorView(mAnchorView);
1051e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setAdapter(mAlternatesAdapter);
1052e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setWidth(getWidth());
1053e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.setOnItemClickListener(this);
1054e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                mAlternatesPopup.show();
1055c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1056c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1057c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1058c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void setSelected(boolean selected) {
1059c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelected = selected;
1060c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1061c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the text displayed in the chip.
10648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1065c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getDisplay() {
1066c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDisplay;
1067c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1068c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the text value this chip represents.
10718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1072c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getValue() {
1073c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mValue;
1074c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1075c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * See if a touch event was inside the delete target of
10788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * a selected chip. It is in the delete target if:
10798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * 1) the x and y points of the event are within the
10808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * delete assset.
10818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * 2) the point tapped would have caused a cursor to appear
10828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * right after the selected chip.
10838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean isInDelete(int offset, float x, float y) {
1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Figure out the bounds of this chip and whether or not
1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // the user clicked in the X portion.
1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mSelected
1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    && (offset == getChipEnd()
1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                            || (x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right));
1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
10938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Return whether this chip contains the position passed in.
10948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public boolean matchesChip(int offset) {
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = getChipStart();
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getChipEnd();
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return (offset >= start && offset <= end);
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
11028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * When a selected chip receives a click event, see if that event was in
11038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * the delete icon. If so, delete it. Otherwise, unselect the chip.
11048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onClick(View widget, int offset, float x, float y) {
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mSelected) {
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (isInDelete(offset, x, y)) {
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    removeChip();
11091a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                } else {
11101a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                    clearSelectedChip();
1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                int y, int bottom, Paint paint) {
1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Shift the bounds of this span to where it is actually drawn on the screeen.
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mLeft = (int) x;
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
11248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Handles clicks to alternate addresses for a selected chip. If the user
11258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * selects an alternate, the chip is replaced with a new contact with the
11268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * new contact address information.
11278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) {
1130e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            mAlternatesPopup.dismiss();
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replaceChip(mAlternatesAdapter.getRecipientEntry(position));
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
11368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the id of the contact associated with this chip.
11378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getContactId() {
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mContactId;
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        /**
11438684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         * Get the id of the data associated with this chip.
11448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira         */
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getDataId() {
1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDataId;
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
11482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11492d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira}
1150