RecipientEditTextView.java revision b5ebb8916a1bb178062a0ccb59e80de27ec1bb2d
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;
202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Bitmap;
212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Canvas;
222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Paint;
232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.Rect;
242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.BitmapDrawable;
252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.os.Handler;
272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Layout;
292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Spannable;
302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.SpannableString;
312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Spanned;
322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.TextPaint;
332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.TextUtils;
342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.method.QwertyKeyListener;
352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.style.ImageSpan;
362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
37078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereiraimport android.util.Log;
382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.view.KeyEvent;
392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.view.MotionEvent;
402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.view.View;
412d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.AdapterView;
422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
43b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereiraimport android.widget.Filter.FilterListener;
442d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.ListPopupWindow;
452d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
46b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport android.widget.PopupWindow.OnDismissListener;
47b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
48b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
49b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.HashSet;
50b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.List;
51b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Set;
522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
530f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereiraimport java.util.ArrayList;
540f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView
602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    implements OnItemClickListener {
612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
62078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    private static final String TAG = "RecipientEditTextView";
632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
64078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    private Drawable mChipBackground = null;
652d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
6643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    private Drawable mChipDelete = null;
6743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
68078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    private int mChipPadding;
692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    private Tokenizer mTokenizer;
712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    private final Handler mHandler;
732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    private Runnable mDelayedSelectionMode = new Runnable() {
752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        @Override
762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public void run() {
772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            setSelection(getText().length());
782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    };
802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
8143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    private Drawable mChipBackgroundPressed;
8243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
8343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    private RecipientChip mSelectedChip;
8443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
8543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    private int mChipDeleteWidth;
8643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
870f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira    private ArrayList<RecipientChip> mRecipients;
880f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        mHandler = new Handler();
922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        setOnItemClickListener(this);
930f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira        mRecipients = new ArrayList<RecipientChip>();
942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
96f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
97078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        throws NullPointerException {
98078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        if (mChipBackground == null) {
99078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            throw new NullPointerException
100078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                ("Unable to render any chips as setChipDimensions was not called.");
101078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        }
102f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        String text = contact.getDisplayName();
1032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        Layout layout = getLayout();
1042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        int line = layout.getLineForOffset(offset);
1052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        int lineTop = layout.getLineTop(line);
1062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        TextPaint paint = getPaint();
1082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        float defaultSize = paint.getTextSize();
1092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // Reduce the size of the text slightly so that we can get the "look" of
1112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // padding.
1122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        paint.setTextSize((float) (paint.getTextSize() * .9));
1132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
1152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
1162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // on the sides.
11743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        CharSequence ellipsizedText = TextUtils.ellipsize(text, paint,
11843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                calculateAvailableWidth(pressed), TextUtils.TruncateAt.END);
1192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        int height = getLineHeight();
1212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        int width = (int) Math.floor(paint.measureText(ellipsizedText, 0, ellipsizedText.length()))
122078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                + (mChipPadding * 2);
12343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        if (pressed) {
12443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            width += mChipDeleteWidth;
12543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
1262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // Create the background of the chip.
1282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
13043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        if (pressed) {
13143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (mChipBackgroundPressed != null) {
13243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mChipBackgroundPressed.setBounds(0, 0, width, height);
13343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mChipBackgroundPressed.draw(canvas);
13443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
13543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                // Align the display text with where the user enters text.
13643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
13743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        - layout.getLineDescent(line), paint);
13843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mChipDelete.setBounds(width - mChipDeleteWidth, 0, width, height);
13943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mChipDelete.draw(canvas);
14043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            } else {
14143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                Log.w(TAG,
14243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        "Unable to draw a background for the chips as it was never set");
14343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
1442d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        } else {
14543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (mChipBackground != null) {
14643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mChipBackground.setBounds(0, 0, width, height);
14743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mChipBackground.draw(canvas);
14843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
14943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                // Align the display text with where the user enters text.
15043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
15143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        - layout.getLineDescent(line), paint);
15243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            } else {
15343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                Log.w(TAG,
15443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        "Unable to draw a background for the chips as it was never set");
15543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
1562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
1572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // Get the location of the widget so we can properly offset
1602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // the anchor for each chip.
1612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        int[] xy = new int[2];
1622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        getLocationOnScreen(xy);
1632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // Pass the full text, un-ellipsized, to the chip.
1642d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
1652d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        result.setBounds(0, 0, width, height);
166f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + width,
167f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira                calculateLineBottom(xy[1], line));
168f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds);
1692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // Return text to the original size.
1712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        paint.setTextSize(defaultSize);
1722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
1732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        return recipientChip;
1742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
1752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
176f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    // The bottom of the line the chip will be located on is calculated by 4 factors:
177f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    // 1) which line the chip appears on
178f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    // 2) the height of a line in the autocomplete view
179f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    // 3) padding built into the edit text view will move the bottom position
180f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    // 4) the position of the autocomplete view on the screen, taking into account
181f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    // that any top padding will move this down visually
182f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    private int calculateLineBottom(int yOffset, int line) {
183f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        int bottomPadding = 0;
184f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        if (line == getLineCount() - 1) {
185f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira            bottomPadding += getPaddingBottom();
186f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        }
187f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        return ((line + 1) * getLineHeight()) + (yOffset + getPaddingTop()) + bottomPadding;
188f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
189f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
1902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    // Get the max amount of space a chip can take up. The formula takes into
1912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    // account the width of the EditTextView, any view padding, and padding
1922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    // that will be added to the chip.
19343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    private float calculateAvailableWidth(boolean pressed) {
19443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        int paddingRight = 0;
19543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        if (pressed) {
19643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            paddingRight = mChipDeleteWidth;
19743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
19843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2)
19943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                - paddingRight;
2002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
202078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    /**
203078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira     * Set all chip dimensions and resources. This has to be done from the application
204078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira     * as this is a static library.
205078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira     * @param chipBackground drawable
206078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira     * @param padding Padding around the text in a chip
207078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira     * @param offset Offset between the chip and the dropdown of alternates
208078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira     */
20943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
21043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            Drawable chipDelete, float padding) {
211078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        mChipBackground = chipBackground;
21243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
21343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        mChipDelete = chipDelete;
21443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        mChipDeleteWidth = chipDelete.getIntrinsicWidth();
215078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        mChipPadding = (int) padding;
2162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    @Override
2192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
2202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        mTokenizer = tokenizer;
2212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super.setTokenizer(mTokenizer);
2222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    // We want to handle replacing text in the onItemClickListener
2252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    // so we can get all the associated contact information including
2262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    // display text, address, and id.
2272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    @Override
2282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    protected void replaceText(CharSequence text) {
2292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        return;
2302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    @Override
2332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
2342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        switch (keyCode) {
2352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            case KeyEvent.KEYCODE_ENTER:
2362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
2372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            case KeyEvent.KEYCODE_TAB:
2382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                if (event.hasNoModifiers()) {
239078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                    if (isPopupShowing()) {
240078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                        // choose the first entry.
241078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                        submitItemAtPosition(0);
242078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                        dismissDropDown();
2432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        return true;
2442d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                    } else {
2452d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        int end = getSelectionEnd();
2462d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        int start = mTokenizer.findTokenStart(getText(), end);
2472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        String text = getText().toString().substring(start, end);
2482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        clearComposingText();
2492d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        Editable editable = getText();
251f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira                        RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
2522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        QwertyKeyListener.markAsReplaced(editable, start, end, "");
253f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira                        editable.replace(start, end, createChip(entry));
2542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                        dismissDropDown();
2552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                    }
2562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                }
2572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
2582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        return super.onKeyUp(keyCode, event);
2592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2602d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public void onChipChanged() {
2622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // Must be posted so that the previous span
2632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // is correctly replaced with the previous selection points.
2642d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        mHandler.post(mDelayedSelectionMode);
2652d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2662d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2672d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    @Override
2682d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
2692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        int start = getSelectionStart();
2702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        int end = getSelectionEnd();
2712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        Spannable span = getSpannable();
2722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
2742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        if (chips != null) {
2752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            for (RecipientChip chip : chips) {
2762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                chip.onKeyDown(keyCode, event);
2772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            }
2782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
2792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
2812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            return true;
2822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
2832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        return super.onKeyDown(keyCode, event);
2852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
287078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    private Spannable getSpannable() {
2882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        return (Spannable) getText();
2892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
291078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
292078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    @Override
293078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
294078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        int action = event.getAction();
295b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira        boolean handled = super.onTouchEvent(event);
29643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        boolean chipWasSelected = false;
2970f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
298078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
299078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            Spannable span = getSpannable();
300078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            int offset = getOffsetForPosition(event.getX(), event.getY());
30143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            int end = span.nextSpanTransition(offset, span.length(), RecipientChip.class);
30243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            int start = mTokenizer.findTokenStart(span, end);
303078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
304078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            if (chips != null && chips.length > 0) {
305078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                // Get the first chip that matched.
306078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                final RecipientChip currentChip = chips[0];
307078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
308078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                if (action == MotionEvent.ACTION_UP) {
30943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
31043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        mSelectedChip.unselectChip();
31143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        mSelectedChip = currentChip.selectChip();
31243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    } else if (mSelectedChip == null) {
31343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        mSelectedChip = currentChip.selectChip();
31443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    } else {
31543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                        mSelectedChip.onClick(this, event.getX(), event.getY());
31643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    }
317078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                }
31843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                chipWasSelected = true;
31943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
32043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
32143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        if (!chipWasSelected) {
32243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (mSelectedChip != null) {
32343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mSelectedChip.unselectChip();
32443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mSelectedChip = null;
325078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            }
326078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        }
327b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira        return handled;
328078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    }
329078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
330f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    private CharSequence createChip(RecipientEntry entry) {
331078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        // We want to override the tokenizer behavior with our own ending
332078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        // token, space.
333f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        SpannableString chipText = new SpannableString(mTokenizer.terminateToken(entry
334f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira                .getDisplayName()));
335078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        int end = getSelectionEnd();
336078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
337078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        try {
338f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira            chipText.setSpan(constructChipSpan(entry, start, false), 0, entry.getDisplayName()
339f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira                    .length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
340078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        } catch (NullPointerException e) {
34143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            Log.e(TAG, e.getMessage(), e);
342078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            return null;
343078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        }
344078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
345078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        return chipText;
346078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    }
347078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
348078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    @Override
349078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
350078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        submitItemAtPosition(position);
351078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    }
352078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
353078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    private void submitItemAtPosition(int position) {
354f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position);
355078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        clearComposingText();
356078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
357078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        int end = getSelectionEnd();
358078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
359078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
360078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        Editable editable = getText();
361f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        editable.replace(start, end, createChip(entry));
362078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
363078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira    }
364078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira
3650f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira    public Editable getRecipients() {
3660f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira        StringBuilder plainText = new StringBuilder();
3670f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira        int size = mRecipients.size();
3680f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira        for (int i = 0; i < size; i++) {
3690f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira            plainText.append(mRecipients.get(i).getValue());
3700f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira                if (i != size-1) {
3710f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira                    plainText.append(',');
3720f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira                }
3730f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira        }
3740f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira        return Editable.Factory.getInstance().newEditable(plainText);
3750f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira    }
3760f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira
377b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa    /** Returns a collection of contact Id for each chip inside this View. */
378b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa    /* package */ Collection<Integer> getContactIds() {
379b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        final Set<Integer> result = new HashSet<Integer>();
380b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        for (RecipientChip chip : mRecipients) {
381b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa            result.add(chip.getContactId());
382b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        }
383b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        return result;
384b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa    }
385b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
386b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa    /** Returns a collection of data Id for each chip inside this View. May be null. */
387b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa    /* package */ Collection<Integer> getDataIds() {
388b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        final Set<Integer> result = new HashSet<Integer>();
389b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        for (RecipientChip chip : mRecipients) {
390b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa            result.add(chip.getDataId());
391b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        }
392b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        return result;
393b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa    }
394b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
3952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    /**
3962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira     * RecipientChip defines an ImageSpan that contains information relevant to
3972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira     * a particular recipient.
3982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira     */
399b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira    public class RecipientChip extends ImageSpan implements OnItemClickListener, OnDismissListener,
400b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira            FilterListener {
4012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        private final CharSequence mDisplay;
4022d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        private final CharSequence mValue;
4042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        private final int mOffset;
4062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        private ListPopupWindow mPopup;
4082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        private View mAnchorView;
4102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        private int mLeft;
4122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
413b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        private final int mContactId;
414b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
415b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        private final int mDataId;
4162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
41743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        private RecipientEntry mEntry;
41843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
41943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        private boolean mSelected = false;
42043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
42143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        private Rect mBounds;
422f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira        public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) {
4232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            super(drawable);
424f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira            mDisplay = entry.getDisplayName();
425f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira            mValue = entry.getDestination();
426b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa            mContactId = entry.getContactId();
427b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa            mDataId = entry.getDataId();
4282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mOffset = offset;
42943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            mEntry = entry;
43043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            mBounds = bounds;
431f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
4322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mAnchorView = new View(getContext());
4332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mAnchorView.setLeft(bounds.left);
4342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mAnchorView.setRight(bounds.left);
435f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira            mAnchorView.setTop(bounds.bottom);
436f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira            mAnchorView.setBottom(bounds.bottom);
4372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mAnchorView.setVisibility(View.GONE);
4380f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira            mRecipients.add(this);
4392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
4402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
44143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        public void unselectChip() {
44243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (getChipStart() == -1 || getChipEnd() == -1) {
44343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mSelectedChip = null;
44443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                return;
44543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
44643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            clearComposingText();
44743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            RecipientChip newChipSpan = null;
44843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            try {
44943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                newChipSpan = constructChipSpan(mEntry, mOffset, false);
45043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            } catch (NullPointerException e) {
45143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                Log.e(TAG, e.getMessage(), e);
45243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                return;
45343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
45443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            replace(newChipSpan);
45543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (mPopup != null && mPopup.isShowing()) {
45643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mPopup.dismiss();
45743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
45843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            return;
45943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
46043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
4612d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public void onKeyDown(int keyCode, KeyEvent event) {
4622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            if (keyCode == KeyEvent.KEYCODE_DEL) {
4632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                if (mPopup != null && mPopup.isShowing()) {
4642d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                    mPopup.dismiss();
4652d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                }
4662d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                removeChip();
4672d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            }
4682d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
4692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public boolean isCompletedContact() {
471b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa            return mContactId != -1;
4722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
4732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        private void replace(RecipientChip newChip) {
4752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            Spannable spannable = getSpannable();
4762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            int spanStart = getChipStart();
4772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            int spanEnd = getChipEnd();
4782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, "");
4792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            spannable.removeSpan(this);
4800f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira            mRecipients.remove(this);
4812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            spannable.setSpan(newChip, spanStart, spanEnd, 0);
4822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
4832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public void removeChip() {
4852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            Spannable spannable = getSpannable();
4862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            int spanStart = getChipStart();
4872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            int spanEnd = getChipEnd();
48843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
4892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, "");
4902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            spannable.removeSpan(this);
4910f93fba1c7da8cb2d209e5e37831600c3b86852cMindy Pereira            mRecipients.remove(this);
49243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            spannable.setSpan(null, spanStart, spanEnd, 0);
493b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira            getText().delete(spanStart, spanEnd);
4942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
4952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public int getChipStart() {
4972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            return getSpannable().getSpanStart(this);
4982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
4992d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public int getChipEnd() {
5012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            return getSpannable().getSpanEnd(this);
5022d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
5032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
504b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira        public void replaceChip(RecipientEntry entry) {
5052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            clearComposingText();
5062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
507078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            RecipientChip newChipSpan = null;
508078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            try {
509b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira                newChipSpan = constructChipSpan(entry, mOffset, false);
510078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            } catch (NullPointerException e) {
51143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                Log.e(TAG, e.getMessage(), e);
512078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira                return;
513078509f1fd42ec04b46565ecc26f4f527b277c5cMindy Pereira            }
5142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            replace(newChipSpan);
5152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            if (mPopup != null && mPopup.isShowing()) {
5162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                mPopup.dismiss();
5172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            }
5182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            onChipChanged();
5192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
5202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
52143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        public RecipientChip selectChip() {
52243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            clearComposingText();
52343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            RecipientChip newChipSpan = null;
52443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (isCompletedContact()) {
52543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                try {
52643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    newChipSpan = constructChipSpan(mEntry, mOffset, true);
52743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    newChipSpan.setSelected(true);
52843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                } catch (NullPointerException e) {
52943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    Log.e(TAG, e.getMessage(), e);
53043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    return newChipSpan;
53143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                }
53243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                replace(newChipSpan);
53343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                if (mPopup != null && mPopup.isShowing()) {
53443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    mPopup.dismiss();
53543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                }
53643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mSelected = true;
53743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                // Make sure we call edit on the new chip span.
53843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                newChipSpan.showAlternates();
53943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            } else {
54043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                CharSequence text = getValue();
54143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                removeChip();
54243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                Editable editable = getText();
54343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                setSelection(editable.length());
54443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                editable.append(text);
54543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
54643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            return newChipSpan;
54743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
54843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
54943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        private void showAlternates() {
55043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            mPopup = new ListPopupWindow(RecipientEditTextView.this.getContext());
55143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
55243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (!mPopup.isShowing()) {
55343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mAnchorView.setLeft(mLeft);
55443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mAnchorView.setRight(mLeft);
55543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mPopup.setAnchorView(mAnchorView);
55643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
55743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                adapter.getFilter().filter(getValue(), this);
55843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mPopup.setAdapter(adapter);
55943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                // TODO: get width from dimen.xml.
56043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mPopup.setWidth(getWidth());
56143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mPopup.setOnItemClickListener(this);
56243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                mPopup.setOnDismissListener(this);
56343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            }
56443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
56543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
56643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        private void setSelected(boolean selected) {
56743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            mSelected = selected;
56843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
56943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
5702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public CharSequence getDisplay() {
5712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            return mDisplay;
5722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
5732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public CharSequence getValue() {
5752d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            return mValue;
5762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
5772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
57843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        private boolean isInDelete(float x, float y) {
57943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            // Figure out the bounds of this chip and whether or not
58043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            // the user clicked in the X portion.
58143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            return x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right;
58243876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        }
58343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira
58443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira        public void onClick(View widget, float x, float y) {
58543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira            if (mSelected) {
58643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                if (isInDelete(x, y)) {
58743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    removeChip();
58843876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira                    return;
589f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira                }
5902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            }
5912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
5922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5932d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        @Override
5942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
5952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira                int y, int bottom, Paint paint) {
5962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mLeft = (int) x;
5972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            super.draw(canvas, text, start, end, x, top, y, bottom, paint);
5982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
5992d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
6002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        @Override
6012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) {
6022d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mPopup.dismiss();
6032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            clearComposingText();
604b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira            replaceChip((RecipientEntry) adapterView.getItemAtPosition(position));
6052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
6062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
6072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        // When the popup dialog is dismissed, return the cursor to the end.
6082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        @Override
6092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        public void onDismiss() {
6102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira            mHandler.post(mDelayedSelectionMode);
6112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        }
612b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira
613b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira        @Override
614b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira        public void onFilterComplete(int count) {
615b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira            if (count > 0 && mPopup != null) {
616b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira                mPopup.show();
617b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira            }
618b5dd668995d80a9bc9a6c5ed9f24a9d32a269634Mindy Pereira        }
619b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
620b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        public int getContactId() {
621b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa            return mContactId;
622b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        }
623b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
624b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        public int getDataId() {
625b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa            return mDataId;
626b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa        }
6272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
6282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira}
629