RecipientEditTextView.java revision b8208f24b2768acf369ad58309031feac87ce79c
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
19b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.app.Dialog;
20b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipData;
21b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager;
222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context;
23b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface;
24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener;
25c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap;
261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory;
27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix;
29c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
3377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask;
34156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler;
35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message;
362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
37c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
38c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
44c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
492d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
50c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
514fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
52a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
53b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.View.OnClickListener;
54b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
56c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
61416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
62c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
64cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable;
65b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
67156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
682d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
69416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
70c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
71b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
72a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
73b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
7477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
75c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
76c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
77c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
82b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
83b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
84b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener {
85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
86c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
87c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
95c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
96c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
109c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
111054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
112a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
113e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
114e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
115e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
116e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
119045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
120045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
121156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
122156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
123156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
124156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
125156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
126156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
127c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
128c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
12977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
13205522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
13305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
134c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
135e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
136dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
137b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
138b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
14277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
145bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
146bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
147b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
148b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
149b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
150b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
151b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
156b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
157a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
158a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
159b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
161b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
162416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
163e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
164e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
165416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
166416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
167416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
168416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
169e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
172e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
173e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
17977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
181a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
182a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
183a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
184a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
185a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
186a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
1902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
19277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
19377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
19477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
195b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
1961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
197b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
198b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
200b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
201368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
202b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
205368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
210d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        setSuggestionsEnabled(false);
211c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
213156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
214156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
215156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
216156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
218156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
219156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
220156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
221156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
222156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2236ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2246ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
225b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
226ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
227cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
228ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
229ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
230ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
231ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
232ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
233ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
24083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
24183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
24283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
24383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
248d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
249d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
250d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
251d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
252c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
253c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
254c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
255c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
256c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
257c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
258c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
259c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
260001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
261001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
262001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
263001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
264c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
265c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
2664f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
26705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
268c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
269c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
270c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
271a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
272c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
273c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
274a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
275a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
276a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
277a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
2786ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
279c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
280c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
281d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
282d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
283d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
2844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
2854fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
2864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
287416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
2884fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
289d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
2902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
2934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mSelectedChip != null) {
2944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
2954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
296001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
297001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
298001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
299a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
300001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
301001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
302001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
303001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
304001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
305001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(getText(), start);
307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
308001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    if (whatEnd != getSelectionEnd()) {
310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
311001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
315090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
32677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
32777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
32877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
32977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
33077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
33177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
335e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
336c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
337c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
338c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
339c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
340c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
342c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
343e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
347e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
3491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
3511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
3521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
357c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
3591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
3631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
36477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
3661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
367e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    - Math.abs(height - mChipFontSize)/2, paint);
3681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
3731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
376c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
377045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
378045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
379045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
380045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
381045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
382a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
383045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
384045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
385045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
386e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
390e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
3924221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
3934221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
3944221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
3954221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
3961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
3971a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3981a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
3991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
402c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
406045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
407045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
408045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
409045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4119024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4139024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
41490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
41590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
41690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
41790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
41890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
41990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
42090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
42190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
42290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4239024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4249024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4259024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4269024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4279024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4289024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4299024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4309024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
4319024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
4329024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
433379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira                RectF dst = new RectF(width - iconWidth, 0, width, height);
4349024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
4359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
436c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4379024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
439c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
4401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
442379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
44390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    height - Math.abs(height - mChipFontSize) / 2, paint);
444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
4451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
457c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
4591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
46077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
464e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
467045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
4681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
47377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
47677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
477c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
4782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
481045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
482045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
4835519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
4845519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
4858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
486c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
4875519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
488416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
489b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        return -((actualLine * ((int)mChipHeight) + getPaddingBottom()) + getPaddingTop());
490f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
491f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
4928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
4948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
4958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
4968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
4981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
4992d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5024f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5034f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5044f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5064f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5091426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5111426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
513b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
514b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
51643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
517045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
518d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
519b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
520b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
521b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
522b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
523b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
524b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
525b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
526b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
527b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
528b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
529b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
530b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
531b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
532b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
533b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
534b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
535b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            int alternatesLayout, float chipHeight, float padding,
536d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            float chipFontSize) {
537d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mChipBackground = chipBackground;
538d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
539d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mChipDelete = chipDelete;
540d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mChipPadding = (int) padding;
541d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mAlternatesLayout = alternatesLayout;
542d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mDefaultContactPhoto = defaultContact;
543c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
544d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mChipHeight = chipHeight;
545d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mChipFontSize = chipFontSize;
546d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira        mInvalidChipBackground = invalidChip;
547b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = -1;
548d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira    }
549d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira
550bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
551bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
552bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
553bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
554bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
555bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
556bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
557bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
558bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
559bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
560bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
562c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
563c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
5647bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (width != 0 && height != 0 && mPendingChipsCount > 0) {
5657bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            postHandlePendingChips();
5667bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
56777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
568416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
569416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
570416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
571416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
572416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
573416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
574416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
575416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
576416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
577416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
578c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
579c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
580a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
581a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
582a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
583a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
584a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
5850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void handlePendingChips() {
586a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount <= 0) {
587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            return;
588a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
5897bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
5907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
5917bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
5927bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
5937bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
5947bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
595a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
596a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount);
597a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
598a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
600a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
601a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
603a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
605a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
6110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
6130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            if (mTemporaryRecipients != null
616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
617a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
618a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
620a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
62577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
626a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
629a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
630a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // to
631a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // showing addresses for all of them.
632a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
63377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
63477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
635a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
636a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
6370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
6420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
6600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
6710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
6731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
6741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
6751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
6761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
6771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
678b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
6791e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
6800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
6810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
6820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
684d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
685d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            String destText = entry.getDestination();
686d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            destText = (String) mTokenizer.terminateToken(destText);
687d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
688d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int textLength = destText.length() - 1;
689d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
690d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
691d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
692d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
693d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
694d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
695d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
696d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
697d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
698d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
6990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
700d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
701d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
702d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
703d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
704d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
70577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
7060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
709d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
710d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
711d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
7120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
7131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
7146ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
715454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
716454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
7171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
7181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
7191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
7206ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
7216ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
7226ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
7236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
7246ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
7251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
7261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
7276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
7286ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
7296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
7306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
7316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            token = mValidator.fixText(token).toString();
732490556a764a879cd0eaff358e90705cc1335c92eErik            if (!TextUtils.isEmpty(token)) {
733490556a764a879cd0eaff358e90705cc1335c92eErik                // protect against the case of a validator with a null domain,
734490556a764a879cd0eaff358e90705cc1335c92eErik                // which doesn't add a domain to the token
735490556a764a879cd0eaff358e90705cc1335c92eErik                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token);
736490556a764a879cd0eaff358e90705cc1335c92eErik                if (tokenized.length > 0) {
737490556a764a879cd0eaff358e90705cc1335c92eErik                    token = tokenized[0].getAddress();
738490556a764a879cd0eaff358e90705cc1335c92eErik                }
739d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7406ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
741454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
7421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return RecipientEntry.constructFakeEntry(token);
7431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
7441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
7456ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
7466ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
7476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
7486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
7491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
7501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
7511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
7521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
757c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
758c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
759c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
760c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
761c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
7648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
7658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
7668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
7678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
7688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
7698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
7718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
7728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
7738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
774c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
775c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
776c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
777c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
7818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
782c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
78395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
78495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
78595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
78695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
78795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
78895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
78995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
7908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
7928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
7938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
7948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
7958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
7968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
7978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
79895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
799c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
800c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
801c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
802c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
803c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
804d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
807e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
808e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
809e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
810e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
811e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
812e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
81495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
815e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
816e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
817e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
818e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
819e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
820e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
821e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
822e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
823e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
824e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
825e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
827c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
828c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
829c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
830e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
831e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
832e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
833e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
834e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
835e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
836e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
837e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
838e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
839045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
840045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
841045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
842045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
843045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
844045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
845045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
8468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
8474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
848045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
8494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
850dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
851e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
852e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
853e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
854e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
8584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
863e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
866e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
868e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
8704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
871054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
8734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
875001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
877e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
878e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
879e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
880e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
881e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
883e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
884e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
8851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
886d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
887d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
888d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
889d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    editable.replace(start, end, chipText);
890d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
891d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
8924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
893d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
894d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
8954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
896d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
897d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
8991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
9001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
9011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
9021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
9031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
9041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
9051e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
90605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
9071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
909e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
911e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        CharSequence chipText = createChip(entry, false);
919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        editable.replace(start, getSelectionEnd(), chipText);
920054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
92105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
92205522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
9238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
9258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
9268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
927c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
928c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
929b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
930b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
931b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
932b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
933b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
940c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
9412d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
9422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
944ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
945c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
947b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
948b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
949b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
950b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
951b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
952b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
953b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
954b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
955c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
976c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
980b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
982c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
98336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
984c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
985c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
9888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
9898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
9908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
9918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
9928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
9938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
994c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
995c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
996d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
997d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
998d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
999d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1000d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1002d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
1004b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (action == MotionEvent.ACTION_DOWN && mSelectedChip == null) {
1005b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1006b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1007b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mCopyDialog == null && action == MotionEvent.ACTION_UP) {
1008c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1009c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1011c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1016b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
10188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // Selection may have moved due to the tap event,
10198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // but make sure we correctly reset selection to the
10208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // end so that any unfinished chips are committed.
10218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
10228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1023b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1024c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1025b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1026c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1027c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1029416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1031c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1032c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1033c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1036c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1037c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1038416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1039416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1040c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1041416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1042416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1043416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1044b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1045b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1046b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1047c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
105177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1052416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1053b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1054368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1055b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1056b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1057b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1058b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1059b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1060b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1061b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1062b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1064b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1065b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1066b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1067b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1068b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1069b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1070b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1071b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
10731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
10741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
10751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
10761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
10771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1078a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1079b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1080b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1081b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1082b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1083b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1084b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = position;
1085b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1086b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1087b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
11064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
11074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
11104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1111c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
11194fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
11204fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
11214fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
11224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
11234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
11244fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1126c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1130b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1131b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1132b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1139fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1140c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        String displayText = entry.getDestination();
11414f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira        displayText = (String) mTokenizer.terminateToken(displayText);
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1143b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        int textLength = displayText.length()-1;
1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
1148fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            chipText.setSpan(constructChipSpan(entry, start, pressed), 0, textLength,
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
11608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
11618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
11680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
11690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
11701e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
11711e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
11721e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
11804221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
11814221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
11824221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
11834221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1185c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
11870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
11880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
11890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
11910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
11920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
11930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
11940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1195c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1196c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
11970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
11980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
11990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
12000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
12010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
12020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
12030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
12040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1206c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1207c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
12087a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
12097a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12107a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12117a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
12127a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
121783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
121883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
121983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
122083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
122483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
12257a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12267a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12277a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
12287a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1229c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1230c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1231c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1232c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
12364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
12414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
12454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
12508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
12534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1257045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1258045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1259045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
12608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createMoreChip() {
1262bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1263bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1264bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1265bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
126683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] recipients = getRecipients();
126783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
12680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
12690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
12704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
127183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
12724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
12734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
1274c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1275c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1276c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1277c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1278c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1279c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
12804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
12814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
12824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
12834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
1284c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                morePaint);
12854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
12874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
12884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        ImageSpan moreSpan = new ImageSpan(result);
12894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Spannable spannable = getSpannable();
12904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1291368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
12924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
12930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
12940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
12950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
12964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1297368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
12984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
12994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
1300368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1301368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
13029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1303368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
13049024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1305368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1306368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
13079024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
130877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mTemporaryRecipients != null && !mTemporaryRecipients.contains(recipients[i])) {
130977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                recipients[i].storeChipStart(spannable.getSpanStart(recipients[i]));
131077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                recipients[i].storeChipEnd(spannable.getSpanEnd(recipients[i]));
131177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1312368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
13134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
131477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // TODO: why would these ever be backwards?
131577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
131677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
131777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
13184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
131977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
13200fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
13214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
13258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
13268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
13284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
13294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
13304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
13314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
13324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
13334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
13344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
13354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
13364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
1337b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    int chipStart = chip.getStoredChipStart();
13380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
13390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
13400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    if (chipStart == -1) {
13410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        // Need to find the location of the chip, again.
13420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        token = (String)mTokenizer.terminateToken(chip.getEntry().getDestination());
13430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        chipStart = editable.toString().indexOf(token);
1344b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                        // -1 for the space!
1345b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                        chipEnd = chipStart + token.length() - 1;
13460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    } else {
13470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        chipEnd = Math.min(editable.length(), chip.getStoredChipEnd());
13480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    }
1349b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    if (Log.isLoggable(TAG, Log.DEBUG) && chipEnd != chip.getStoredChipEnd()) {
1350c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                        Log.d(TAG,
1351c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                                "Unexpectedly, the chip ended after the end of the editable text. "
1352b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                                        + "Chip End " + chip.getStoredChipEnd()
1353c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                                        + "Editable length " + editable.length());
1354c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    }
1355bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1356bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1357bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1358bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1359bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
13604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
13614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
13624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
13634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
13644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1367b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1368b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1369b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1370b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1371b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1372b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1373b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1374b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1375b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1376b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1378b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
13791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
13801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
13811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
13821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
13831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
13841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
13851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
13861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return null;
13871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1388b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1389b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1390b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1391b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1392b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1393b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1394b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1395b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1396b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1397b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1398fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1399b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
140083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1401b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
140283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
14038b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
140483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1405b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
14061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1408c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
14091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
14106ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1411b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1412b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
14131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
14141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
14151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
14161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
14171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
14181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
14191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
14201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
14221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1423b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
14241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
14261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
14271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
14281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
14311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
14331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
14356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
14361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1437fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1438b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1439fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
14421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
14431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
14441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
14451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
14461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
14481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
14491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
14501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
14511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
14521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
14531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
14541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
14551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
14561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
14581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
14591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
14601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
14611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
14621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
14631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1464b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1465b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1466b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1467b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1468b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1469b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1470b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1471b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1472b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1473c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1474b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1475b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip being unselected no longer exists but should.");
1476b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1477c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1478b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14798b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
14808b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
14818b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
14828b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14838b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
14848b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14858b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1487b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1488b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1489b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1490b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1492b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1493c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1494c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1495b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1496b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1497b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1498b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1499b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1500b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1501b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1502b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1504b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1505b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1509b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1512b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1513b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1514b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1516b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1517b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1518b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
15223656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1524b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void removeChip(RecipientChip chip) {
1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1528b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1529b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1530b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int toDelete = spanEnd;
1532b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1533b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1536c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Always remove trailing spaces when removing a chip.
15388b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            toDelete++;
1540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1542b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        text.delete(spanStart, toDelete);
1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1549b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1551b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1553b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1556c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1557b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1558b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1559b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1561b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1562b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1564b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1566b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // There may be a space to replace with this chip's new associated
1567b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // space. Check for it.
1568b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            int toReplace = end;
1569b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            while (toReplace >= 0 && toReplace < editable.length()
1570b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                    && editable.charAt(toReplace) == ' ') {
1571b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                toReplace++;
1572b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1573b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            editable.replace(start, toReplace, chipText);
1574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1576b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1577b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1579b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1581b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1582b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1583b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1584b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1586b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1587b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1589b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1590b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1591b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1596368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1597368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1598368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1599368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1600311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1601311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1602311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1603311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1604311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1605311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1606e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1607e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1608e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
16091e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
16101e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
16111e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
16121e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
16131e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
16141e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
16151e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
16161e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
16171e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
16181e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16191e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
16201e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
16211e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16221e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
16231e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
16241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
16251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1626368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1627e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1628e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1629e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mSelectedChip != null) {
1630e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1631e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1632e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1633e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1634e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1635e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1636054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1637e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1638054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
163976ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1640054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1641054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1642054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1643054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1644054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1645054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1646e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1647e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1648e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1649e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1650e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1651e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1652e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1653e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1654e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
1655e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    if (mValidator != null && mValidator.isValid(sub)) {
1656e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1657e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1658e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1659e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1660e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1661e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1662e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1663e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1665e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1666e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1667e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1668e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1669e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1670e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
167177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
167277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
167377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
167477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String displayText = entry.getDestination();
167577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            displayText = (String) mTokenizer.terminateToken(displayText);
167677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
167777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
167877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
167977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
168077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
168177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
168277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
168377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
168477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
168577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
168677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
168777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
168877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
168977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
169077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
169177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
169277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
169377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            RecipientChip[] existingChips = getSpannable().getSpans(0, getText().length(),
169477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    RecipientChip.class);
169577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
169677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
169777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
169877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
169977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
170077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
170177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
170277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
170377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
170477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
170577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
170677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
170777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
170877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
170977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
171577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
171677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
171777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
171877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
171977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
172077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
172177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
172277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
172577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
172677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
173277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1734b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1735b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1736b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
1737b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.setSpan(replacements.get(i), start, end,
1738b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
174177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
174277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
174377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
174477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
174577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
174677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
174777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
174877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
174977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
175077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
175177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
175277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
175377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
175477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
175677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
175777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
175877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
175977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
176077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
176177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
176277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
17691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
17731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
177477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
177577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
177677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
177777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
177877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
177977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
178077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
178177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
178277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
178377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
178677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
1787b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1788b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1789b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
1790b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1791b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1792b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1793b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1794b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1795b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1796b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1797b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1798b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1799b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1800b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
1801b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
1802b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
1803b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1804b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
1805b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
1806b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
1807b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
1808b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
1809b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
1810b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
1811b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1812b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1813b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1814b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
1815b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
1816b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = new Dialog(getContext());
1817b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
1818b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
1819b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
1820b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
1821b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
1822b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
1823b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
1824b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1825b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1826b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1827b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1828b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1830b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1831b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1832b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1833b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
1834b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1835b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1836b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1837b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1838b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
1839b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1840b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1841b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1842b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1843b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1844b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
1845b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
1846b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = null;
1847b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1848b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1849b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1850b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
1851b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
1852b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
1853b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
1854b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
1855b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
1856b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
185777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
1858