RecipientEditTextView.java revision d9b57273c1f5c3bcd94e662136446cd6fd465ebc
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;
21c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
22c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Paint;
23c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
24c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
28c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
29c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
30c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
33c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
34c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
35c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
37c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode.Callback;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
49b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
50b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
52c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
53c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
54c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.ArrayList;
550f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView
614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    implements OnItemClickListener, Callback {
62c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
69c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
70c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
71c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
72c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
73c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
74c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
75c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
76c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
77c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipDeleteWidth;
78c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
79c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private ArrayList<RecipientChip> mRecipients;
80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesSelectedLayout;
840f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
87c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mRecipients = new ArrayList<RecipientChip>();
88d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        setSuggestionsEnabled(false);
89c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
904fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
91c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // When the user starts typing, make sure we unselect any selected
92c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        // chips.
93c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        addTextChangedListener(new TextWatcher() {
94c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
95c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void afterTextChanged(Editable s) {
96c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
97c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
98c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
99c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void onTextChanged(CharSequence s, int start, int before, int count) {
100c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                // Do nothing.
101c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
102c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            @Override
103c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
104c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                if (mSelectedChip != null) {
105c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                    clearSelectedChip();
106c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                    setSelection(getText().length());
107c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira                }
108c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira            }
109c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        });
1104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
1114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
1134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
1144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
1154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
1164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (mRecipients != null && mRecipients.size() > 0) {
1174fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            Spannable span = getSpannable();
1184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            RecipientChip[] chips = span.getSpans(start, getText().length(), RecipientChip.class);
1194fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
1204fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
1214fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                setSelection(chips[chips.length - 1].getChipEnd() + 1);
1224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
123d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
124d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
125d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
126d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
127d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
128d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
129d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
130d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            clearSelectedChip();
131d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // TODO: commit the default when focus changes. Need an API change
132d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // to be able to still get the popup suggestions when focus is lost.
1334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
134d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            setCursorVisible(true);
135d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            Editable text = getText();
136d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            setSelection(text != null && text.length() > 0 ? text.length() : 0);
1374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
138d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
1392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
1402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        throws NullPointerException {
143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mChipBackground == null) {
144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            throw new NullPointerException
145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                ("Unable to render any chips as setChipDimensions was not called.");
146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        String text = contact.getDisplayName();
148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Layout layout = getLayout();
149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int line = layout.getLineForOffset(offset);
150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int lineTop = layout.getLineTop(line);
151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        TextPaint paint = getPaint();
153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        float defaultSize = paint.getTextSize();
154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Reduce the size of the text slightly so that we can get the "look" of
156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // padding.
157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize((float) (paint.getTextSize() * .9));
158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        CharSequence ellipsizedText = TextUtils.ellipsize(text, paint,
163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                calculateAvailableWidth(pressed), TextUtils.TruncateAt.END);
164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int height = getLineHeight();
1661a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
1671a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
1681a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        int width = Math.max(mChipDeleteWidth * 2, (int) Math.floor(paint.measureText(
1691a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                ellipsizedText, 0, ellipsizedText.length()))
1701a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                + (mChipPadding * 2));
171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (pressed) {
176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mChipBackgroundPressed != null) {
177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mChipBackgroundPressed.setBounds(0, 0, width, height);
178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mChipBackgroundPressed.draw(canvas);
1792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                // Align the display text with where the user enters text.
181c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        - layout.getLineDescent(line), paint);
183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mChipDelete.setBounds(width - mChipDeleteWidth, 0, width, height);
184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mChipDelete.draw(canvas);
185c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
186c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Log.w(TAG,
187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        "Unable to draw a background for the chips as it was never set");
188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mChipBackground != null) {
191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mChipBackground.setBounds(0, 0, width, height);
192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mChipBackground.draw(canvas);
193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                // Align the display text with where the user enters text.
195c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        - layout.getLineDescent(line), paint);
197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Log.w(TAG,
199c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        "Unable to draw a background for the chips as it was never set");
200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
201c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Get the location of the widget so we can properly offset
205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // the anchor for each chip.
206c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int[] xy = new int[2];
207c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        getLocationOnScreen(xy);
208c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        result.setBounds(0, 0, width, height);
211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + width,
212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                calculateLineBottom(xy[1], line));
213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds);
214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
217c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
2192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // The bottom of the line the chip will be located on is calculated by 4 factors:
222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // 1) which line the chip appears on
223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // 2) the height of a line in the autocomplete view
224c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // 3) padding built into the edit text view will move the bottom position
225c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // 4) the position of the autocomplete view on the screen, taking into account
226c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // that any top padding will move this down visually
227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int calculateLineBottom(int yOffset, int line) {
228c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int bottomPadding = 0;
229c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (line == getLineCount() - 1) {
230c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            bottomPadding += getPaddingBottom();
231c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
232c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return ((line + 1) * getLineHeight()) + (yOffset + getPaddingTop()) + bottomPadding;
233f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
234f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
235c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // Get the max amount of space a chip can take up. The formula takes into
236c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // account the width of the EditTextView, any view padding, and padding
237c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // that will be added to the chip.
238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int paddingRight = 0;
240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (pressed) {
241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            paddingRight = mChipDeleteWidth;
242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
243c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2)
244c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                - paddingRight;
2452d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2462d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Set all chip dimensions and resources. This has to be done from the application
249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * as this is a static library.
250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param chipBackground drawable
251c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param offset Offset between the chip and the dropdown of alternates
253c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
25443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
255c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Drawable chipDelete, int alternatesLayout, int alternatesSelectedLayout, float padding) {
256c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipBackground = chipBackground;
257c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipDelete = chipDelete;
259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipDeleteWidth = chipDelete.getIntrinsicWidth();
260c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mChipPadding = (int) padding;
261c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mAlternatesLayout = alternatesLayout;
262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mAlternatesSelectedLayout = alternatesSelectedLayout;
263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
264c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
265c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
266c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
267c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
268c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
269c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
270c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // We want to handle replacing text in the onItemClickListener
272c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // so we can get all the associated contact information including
273c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // display text, address, and id.
274c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
276c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
277c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
278c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_TAB:
285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
286d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
292c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
293c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
294d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    // If the popup is showing, the default is the first item in the popup
295d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    // suggestions list. Otherwise, it is whatever the user had typed in.
296d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    private boolean commitDefault() {
297d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (isPopupShowing()) {
298d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // choose the first entry.
299d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            submitItemAtPosition(0);
300d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            dismissDropDown();
301d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return true;
302d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        } else {
303d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            int end = getSelectionEnd();
304d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
305d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            String text = getText().toString().substring(start, end);
306d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            clearComposingText();
307d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            if (text != null && text.length() > 0) {
308d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                Editable editable = getText();
309d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
310d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                QwertyKeyListener.markAsReplaced(editable, start, end, "");
311d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                editable.replace(start, end, createChip(entry));
312d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
313d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
314d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return false;
315d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
316d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
317d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.onKeyDown(keyCode, event);
322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
326c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
3292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return (Spannable) getText();
333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
335c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
336c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
337c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
338c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
339c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
340c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
341c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
342c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
343c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
344c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
345c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
346c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
347c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
355c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
356c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
357c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip.unselectChip();
361c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
362c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
36336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
364c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
365c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
368d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
369d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
370d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
371d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
372d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
373c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
374d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
375c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
376c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
378c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
380c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
382c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
384c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
385c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
386c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip = currentChip.selectChip();
389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mSelectedChip.onClick(this, offset, x, y);
391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
402c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
406c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
409c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4194fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
4204fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
4214fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
423c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4244fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
425c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
426c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
430c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
431c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
4334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
4344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
4354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
4364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
4374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
4384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
439c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
440c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
441c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
442c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chip.matchesChip(offset)) {
445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private CharSequence createChip(RecipientEntry entry) {
452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        CharSequence displayText = mTokenizer.terminateToken(entry.getDestination());
453c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
454c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int textLength = displayText.length();
455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (displayText.charAt(textLength - 1) == ' ') {
456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            textLength--;
457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            displayText = displayText.toString().concat(" ");
459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            textLength = displayText.length();
460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
462c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
463c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            chipText.setSpan(constructChipSpan(entry, start, false), 0, textLength,
466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
472c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
473c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
476c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
477c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
480c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
481c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position);
482c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
483c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
485c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        editable.replace(start, end, createChip(entry));
489c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
490c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
492c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
493c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
494c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
495c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (RecipientChip chip : mRecipients) {
496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            result.add(chip.getContactId());
497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (RecipientChip chip : mRecipients) {
505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            result.add(chip.getDataId());
506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * RecipientChip defines an ImageSpan that contains information relevant to
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * a particular recipient.
513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
514c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira    public class RecipientChip extends ImageSpan implements OnItemClickListener {
515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mDisplay;
516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final CharSequence mValue;
518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final int mOffset;
520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private ListPopupWindow mPopup;
522c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
523c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private View mAnchorView;
524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private int mLeft;
526c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
527c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mContactId;
528c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private final long mDataId;
530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
531c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientEntry mEntry;
532c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
533c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean mSelected = false;
534c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
535c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private RecipientAlternatesAdapter mAlternatesAdapter;
536c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
537c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private Rect mBounds;
538c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) {
540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super(drawable);
541c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDisplay = entry.getDisplayName();
542c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mValue = entry.getDestination();
543c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mContactId = entry.getContactId();
544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mDataId = entry.getDataId();
545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mOffset = offset;
546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mEntry = entry;
547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mBounds = bounds;
548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView = new View(getContext());
550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setLeft(bounds.left);
551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setRight(bounds.left);
552c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setTop(bounds.bottom);
553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setBottom(bounds.bottom);
554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mAnchorView.setVisibility(View.GONE);
555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mRecipients.add(this);
556c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void unselectChip() {
559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (getChipStart() == -1 || getChipEnd() == -1) {
560c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mSelectedChip = null;
561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
563c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip newChipSpan = null;
565c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            try {
566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                newChipSpan = constructChipSpan(mEntry, mOffset, false);
567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } catch (NullPointerException e) {
568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Log.e(TAG, e.getMessage(), e);
569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replace(newChipSpan);
572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mPopup != null && mPopup.isShowing()) {
573c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mPopup.dismiss();
574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return;
576c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onKeyDown(int keyCode, KeyEvent event) {
579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (keyCode == KeyEvent.KEYCODE_DEL) {
580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (mPopup != null && mPopup.isShowing()) {
581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    mPopup.dismiss();
582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
583c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                removeChip();
584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public boolean isCompletedContact() {
588c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mContactId != -1;
589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void replace(RecipientChip newChip) {
592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable spannable = getSpannable();
593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int spanStart = getChipStart();
594c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int spanEnd = getChipEnd();
595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, "");
596c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.removeSpan(this);
597c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mRecipients.remove(this);
598c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.setSpan(newChip, spanStart, spanEnd, 0);
599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
600c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
601c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void removeChip() {
602c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable spannable = getSpannable();
603c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int spanStart = getChipStart();
604c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int spanEnd = getChipEnd();
605c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Editable text = getText();
606c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int toDelete = spanEnd;
607c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Always remove trailing spaces when removing a chip.
60855bb2833b29945c08b809408ff94ddf7703e911aMindy Pereira            while (toDelete >= 0 && toDelete < text.length() - 1 && text.charAt(toDelete) == ' ') {
609c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                toDelete++;
610c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
611c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, "");
612c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.removeSpan(this);
613c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mRecipients.remove(this);
614c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            spannable.setSpan(null, spanStart, spanEnd, 0);
615c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            text.delete(spanStart, toDelete);
61636d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira            if (this == mSelectedChip) {
61736d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira                mSelectedChip = null;
61836d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira                clearSelectedChip();
61936d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira            }
620c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
622c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipStart() {
623c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return getSpannable().getSpanStart(this);
624c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
625c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
626c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public int getChipEnd() {
627c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return getSpannable().getSpanEnd(this);
628c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
629c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
630c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void replaceChip(RecipientEntry entry) {
631c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
633c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip newChipSpan = null;
634c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            try {
635c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                newChipSpan = constructChipSpan(entry, mOffset, false);
636c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } catch (NullPointerException e) {
637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Log.e(TAG, e.getMessage(), e);
638c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
639c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
640c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replace(newChipSpan);
641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mPopup != null && mPopup.isShowing()) {
642c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mPopup.dismiss();
643c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
645c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
646c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public RecipientChip selectChip() {
647c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
648c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip newChipSpan = null;
649c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (isCompletedContact()) {
650c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                try {
651c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    newChipSpan = constructChipSpan(mEntry, mOffset, true);
652c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    newChipSpan.setSelected(true);
653c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                } catch (NullPointerException e) {
654c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Log.e(TAG, e.getMessage(), e);
655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    return newChipSpan;
656c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
657c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                replace(newChipSpan);
658c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (mPopup != null && mPopup.isShowing()) {
659c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    mPopup.dismiss();
660c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
661c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mSelected = true;
662c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                // Make sure we call edit on the new chip span.
663c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                newChipSpan.showAlternates();
664d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                setCursorVisible(false);
665c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
666c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                CharSequence text = getValue();
667c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                removeChip();
668c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                Editable editable = getText();
669c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                editable.append(text);
670d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                setCursorVisible(true);
671d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                setSelection(editable.length());
672c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
673c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return newChipSpan;
674c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
675c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
676c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void showAlternates() {
677c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mPopup = new ListPopupWindow(RecipientEditTextView.this.getContext());
678c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
679c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (!mPopup.isShowing()) {
680c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAlternatesAdapter = new RecipientAlternatesAdapter(
681c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        RecipientEditTextView.this.getContext(),
682c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mEntry.getContactId(), mEntry.getDataId(),
683c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        mAlternatesLayout, mAlternatesSelectedLayout);
684c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setLeft(mLeft);
685c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mAnchorView.setRight(mLeft);
686c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mPopup.setAnchorView(mAnchorView);
687c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mPopup.setAdapter(mAlternatesAdapter);
688c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mPopup.setWidth(getWidth());
689c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mPopup.setOnItemClickListener(this);
690c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                mPopup.show();
691c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
692c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
693c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
694c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private void setSelected(boolean selected) {
695c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelected = selected;
696c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
697c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
698c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getDisplay() {
699c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDisplay;
700c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
701c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
702c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public CharSequence getValue() {
703c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mValue;
704c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
705c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
706c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        private boolean isInDelete(int offset, float x, float y) {
707c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Figure out the bounds of this chip and whether or not
708c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // the user clicked in the X portion.
709c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mSelected
710c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    && (offset == getChipEnd()
711c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                            || (x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right));
712c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
713c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
714c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public boolean matchesChip(int offset) {
715c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = getChipStart();
716c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getChipEnd();
717c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return (offset >= start && offset <= end);
718c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
719c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
720c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onClick(View widget, int offset, float x, float y) {
721c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (mSelected) {
722c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (isInDelete(offset, x, y)) {
723c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    removeChip();
7241a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                } else {
7251a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira                    clearSelectedChip();
726c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
727c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
728c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
729c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
730c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
731c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
732c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                int y, int bottom, Paint paint) {
733c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Shift the bounds of this span to where it is actually drawn on the screeen.
734c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mLeft = (int) x;
735c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
736c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
737c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
738c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        @Override
739c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) {
740c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mPopup.dismiss();
741c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearComposingText();
742c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            replaceChip(mAlternatesAdapter.getRecipientEntry(position));
743c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
744c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
745c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getContactId() {
746c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mContactId;
747c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
748c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
749c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        public long getDataId() {
750c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return mDataId;
751c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
7522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
7534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
7544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
7554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
7564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return false;
7574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
7584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
7594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
7604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
7614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
7624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
7634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
7644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
7654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return false;
7664fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
7674fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
7684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    // Prevent selection of chips.
7694fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
7704fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
7714fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return false;
7724fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
7732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira}
774c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
775