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