RecipientEditTextView.java revision 572fa15d4b847c2d890b972d4f69d4d2aad5ebd7
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;
37572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
38c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
45c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
53a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
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;
61572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
62416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
65cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable;
66b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
68156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
70416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
71c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
72b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
73a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
74b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
7577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
76c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
77c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
78c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
83b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
84b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
85b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener {
86c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
87c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
88c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
95c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
96c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
110c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
112054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
113a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
114e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
115e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
116e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
117e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
120045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
121045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
122156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
123156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
124156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
125156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
126156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
127156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
128c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
129c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
13077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13205522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
13305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
13405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
135c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
136e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
137dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
138b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
139b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
14377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
146bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
147bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
148b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
149b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
150b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
151b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
157b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
158a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
159a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
161b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
162b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
163416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
164e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
165e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
166416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
167416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
168416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
172e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
173e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
182a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
183a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
184a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
185a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
186a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
1912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
19377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
19477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
19577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
196b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
1971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
198b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
200b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
202368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
205b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
206368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
211572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunne        setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
212c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
214156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
215156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
216156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
217156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
219156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
220156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
221156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
222156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
223156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2246ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2256ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
226b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
227ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
228cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
229ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
230ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
231ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
232ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
233ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
234ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
24183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
24283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
24383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
24483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2464fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
249d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
250d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
251d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
252d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
253c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
254c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
255c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
256c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
257c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
258c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
259c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
260c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
261001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
262001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
263001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
264001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
265c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
266c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
2674f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
26805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
269c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
270c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
271c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
272a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
273c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
275a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
276a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
277a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
278a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
2796ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
280c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
281c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
282d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
283d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
284d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
2854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
2864fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
2874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
288416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
2894fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
290d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
2912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
2944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mSelectedChip != null) {
2954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
2964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
297001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
298001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
299001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
300a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
301001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
302001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
303001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
304001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
305001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(getText(), start);
308001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    if (whatEnd != getSelectionEnd()) {
311001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
315001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
316090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
32777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
32877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
32977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
33077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
33177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
33277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
336e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
337c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
338c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
339c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
340c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
341c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
343c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
344e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
348e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
3511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
3521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
3631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
36577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
3661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
368e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    - Math.abs(height - mChipFontSize)/2, paint);
3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
3731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
3761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
378045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
379045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
380045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
381045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
382045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
383a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
384045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
385045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
386045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
387e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
391e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
3934221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
3944221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
3954221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
3964221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
3971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
3981a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3991a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
406c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
407045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
408045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
409045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
410045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4129024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4149024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
41590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
41690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
41790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
41890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
41990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
42090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
42190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
42290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
42390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4249024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4259024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4269024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4279024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4289024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4299024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4309024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4319024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
4329024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
4339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
434379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira                RectF dst = new RectF(width - iconWidth, 0, width, height);
4359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
4369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
437c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4399024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
4411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
443379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
44490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    height - Math.abs(height - mChipFontSize) / 2, paint);
445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
4461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
458c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
4591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
4601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
46177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
4641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
465e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
468045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
4691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
472c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
47477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
476c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
47777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
4792d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
482045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
483045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
4845519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
4855519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
4868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
487c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
4885519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
489416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
490b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        return -((actualLine * ((int)mChipHeight) + getPaddingBottom()) + getPaddingTop());
491f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
492f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
4938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
4958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
4968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
4978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
4991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5034f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5044f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5054f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5074f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5101426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5121426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
514b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
515b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
51743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
518045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
519d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
520b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
521b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
522b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
523b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
524b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
525b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
526b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
527b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
528b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
529b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
530b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
531b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
532b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
533b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
534bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
535bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
536bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
537bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
538bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
539bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
540bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
541bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
542bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
543bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
544bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
546c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
547c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
5487bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (width != 0 && height != 0 && mPendingChipsCount > 0) {
5497bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            postHandlePendingChips();
5507bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
55177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
552416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
553416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
554416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
555416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
556416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
557416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
558416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
559416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
560416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
561416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
562c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
563c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
564a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
565a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
566a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
567a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
568a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
5690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void handlePendingChips() {
570a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount <= 0) {
571a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            return;
572a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
5737bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
5747bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
5757bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
5767bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
5777bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
5787bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
579a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
580a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount);
581a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
582a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
583a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
584a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
585a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
586a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
588a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
589a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
590a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
591a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
592a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
593a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
594a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
5950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
596a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
5970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
598a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            if (mTemporaryRecipients != null
600a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
601a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
603a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
605a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
60977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // to
615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // showing addresses for all of them.
616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
61777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
61877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
620a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
6210fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6220fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
6260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
6280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
6290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
6300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
6310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
6320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
6330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
6340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
6350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
6360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
6370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
6390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
6400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
6410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
6420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
6430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
6571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
6581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
6591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
6601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
6611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
662b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
6631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
668d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
669d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            String destText = entry.getDestination();
670d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            destText = (String) mTokenizer.terminateToken(destText);
671d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
672d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int textLength = destText.length() - 1;
673d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
674d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
675d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
676d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
677d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
678d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
679d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
680d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
681d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
682d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
6830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
684d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
685d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
686d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
687d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
688d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
68977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
6900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
693d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
694d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
695d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
6960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
6971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
6986ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
699454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
700454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
7011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
7021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
7031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
7046ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
7056ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
7066ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
7076ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
7086ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
7091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
7101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
7116ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
7126ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
7136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
7146ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
7156ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            token = mValidator.fixText(token).toString();
716490556a764a879cd0eaff358e90705cc1335c92eErik            if (!TextUtils.isEmpty(token)) {
717490556a764a879cd0eaff358e90705cc1335c92eErik                // protect against the case of a validator with a null domain,
718490556a764a879cd0eaff358e90705cc1335c92eErik                // which doesn't add a domain to the token
719490556a764a879cd0eaff358e90705cc1335c92eErik                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token);
720490556a764a879cd0eaff358e90705cc1335c92eErik                if (tokenized.length > 0) {
721490556a764a879cd0eaff358e90705cc1335c92eErik                    token = tokenized[0].getAddress();
722490556a764a879cd0eaff358e90705cc1335c92eErik                }
723d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7246ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
725454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
7261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return RecipientEntry.constructFakeEntry(token);
7271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
7281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
7296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
7306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
7316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
7326ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
7331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
7341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
7351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
7361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
741c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
742c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
743c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
744c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
745c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
746c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
7488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
7498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
7508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
7518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
7528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
7538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
7558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
7568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
7578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
758c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
759c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
760c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
761c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
7658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
766c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
76795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
76895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
76995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
77095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
77195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
77295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
77395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
7748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
7768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
7778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
7788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
7798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
7808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
7818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
78295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
783c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
784c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
785c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
786c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
787c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
788d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
789c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
790c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
791e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
792e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
793e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
794e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
795e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
796e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
797c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
79895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
799e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
800e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
801e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
802e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
803e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
804e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
805e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
806e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
807e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
808e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
809e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
812c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
814e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
815e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
816e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
817e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
818e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
819e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
820e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
821e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
822e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
823045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
824045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
825045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
826045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
827045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
828045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
829045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
8308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
8314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
832045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
8334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
834dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
835e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
836e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
837e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
838e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
839e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
840e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
841e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
8424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
843e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
844e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
845e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
846e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
847e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
848e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
849e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
850e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
851e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
852e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
853e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
8544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
855054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
8574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
858e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
859001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
863e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
866e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
868e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
8691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
870d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
871d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
872d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
873d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    editable.replace(start, end, chipText);
874d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
875d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
8764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
877d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
878d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
8794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
880d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
881d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
8831e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
8841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
8851e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
8861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
8871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
8881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
8891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
89005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
8911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
894e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
895e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
896e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
897e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
899e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        CharSequence chipText = createChip(entry, false);
903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        editable.replace(start, getSelectionEnd(), chipText);
904054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
90505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
90605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
9108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
912c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
913b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
914b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
915b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
916b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
917b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
919c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
920c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
922c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
923c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
924c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
9252d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
9262d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
927c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
928ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
930c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
931b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
932b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
933b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
934b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
935b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
936b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
937b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
938b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
940c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
941c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
942c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
944c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
945c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
947c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
950c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
951c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
952c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
953c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
954c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
955c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
964b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
96736d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
9728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
9738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
9748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
9758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
9768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
9778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
980d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
981d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
982d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
983d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
984d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
985c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
986d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
987c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
988b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (action == MotionEvent.ACTION_DOWN && mSelectedChip == null) {
989b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
990b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
991b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mCopyDialog == null && action == MotionEvent.ACTION_UP) {
992c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
993c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
994c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
995c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
997c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
998c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
999c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1000b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
10028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // Selection may have moved due to the tap event,
10038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // but make sure we correctly reset selection to the
10048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // end so that any unfinished chips are committed.
10058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
10068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1007b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1008c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1009b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1011c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1013416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1016c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1018c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1022416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1023416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1024c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1025416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1026416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1027416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1028b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1029b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1030b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1031c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1032b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1033b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1034b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
103577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1036416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1037b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1038368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1039b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1040b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1041b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1042b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1043b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1044b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1045b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1046b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1047b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1051b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1052b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1053b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1054b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1055b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1056b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
10571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
10581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
10591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
10601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
10611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1062a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1064b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1065b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1066b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1067b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1068b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = position;
1069b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1070b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1071b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1072c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1073c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1074c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1075c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1077c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1079c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1080c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1081c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10894fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
10904fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
10914fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
10944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1095c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11024fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
11034fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
11044fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
11054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
11064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
11074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
11084fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1110c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1114b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1123fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1124c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        String displayText = entry.getDestination();
11254f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira        displayText = (String) mTokenizer.terminateToken(displayText);
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1127b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        int textLength = displayText.length()-1;
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
1132fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            chipText.setSpan(constructChipSpan(entry, start, pressed), 0, textLength,
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11438684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
11448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
11458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
11520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
11530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
11541e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
11551e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
11561e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
11644221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
11654221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
11664221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
11674221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
11710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
11720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
11730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
11750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
11760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
11770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
11780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1179c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1180c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
11810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
11820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
11830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
11840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
11850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
11870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
11880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
11927a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
11937a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
11947a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
11957a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
11967a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1199c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
120183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
120283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
120383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
120483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
1205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1206c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1207c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
120883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
12097a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12107a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12117a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
12127a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
12204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
12254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
12294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
12348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
12374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1241045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1242045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1243045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
12448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createMoreChip() {
1246bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1247bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1248bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1249bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
125083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] recipients = getRecipients();
125183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
12520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
12530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
12544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
125583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
12564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
12574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
1258c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1259c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1260c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1261c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1262c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1263c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
12644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
12654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
12664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
12674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
1268c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                morePaint);
12694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
12714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
12724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        ImageSpan moreSpan = new ImageSpan(result);
12734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Spannable spannable = getSpannable();
12744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1275368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
12764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
12770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
12780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
12790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
12804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1281368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
12824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
12834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
1284368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1285368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
12869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1287368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
12889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1289368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1290368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
12919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
129277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mTemporaryRecipients != null && !mTemporaryRecipients.contains(recipients[i])) {
129377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                recipients[i].storeChipStart(spannable.getSpanStart(recipients[i]));
129477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                recipients[i].storeChipEnd(spannable.getSpanEnd(recipients[i]));
129577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1296368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
12974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
129877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // TODO: why would these ever be backwards?
129977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
130077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
130177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
13024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
130377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
13040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
13054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
13098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
13108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
13124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
13134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
13144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
13154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
13164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
13174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
13184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
13194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
13204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
1321b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    int chipStart = chip.getStoredChipStart();
13220fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
13230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
13240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    if (chipStart == -1) {
13250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        // Need to find the location of the chip, again.
13260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        token = (String)mTokenizer.terminateToken(chip.getEntry().getDestination());
13270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        chipStart = editable.toString().indexOf(token);
1328b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                        // -1 for the space!
1329b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                        chipEnd = chipStart + token.length() - 1;
13300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    } else {
13310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                        chipEnd = Math.min(editable.length(), chip.getStoredChipEnd());
13320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    }
1333b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    if (Log.isLoggable(TAG, Log.DEBUG) && chipEnd != chip.getStoredChipEnd()) {
1334c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                        Log.d(TAG,
1335c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                                "Unexpectedly, the chip ended after the end of the editable text. "
1336b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                                        + "Chip End " + chip.getStoredChipEnd()
1337c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                                        + "Editable length " + editable.length());
1338c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    }
1339bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1340bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1341bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1342bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1343bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
13444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
13454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
13464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
13474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
13484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1351b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1352b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1353b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1354b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1355b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1356b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1357b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1358b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1359b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1360b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1361c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1362b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
13631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
13641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
13651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
13661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
13671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
13681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
13691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
13701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return null;
13711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1372b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1373b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1374b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1375b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1376b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1377b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1378b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1379b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1380b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1381b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1382fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1383b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
138483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1385b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
138683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
13878b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
138883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1389b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
13901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
13911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1392c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
13931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
13946ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1395b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1396b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
13971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
13981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
13991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
14001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
14011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
14021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
14031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
14041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
14061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1407b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
14081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
14101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
14111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
14121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
14151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
14171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
14196ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
14201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1421fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1422b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1423fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
14261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
14271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
14281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
14291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
14301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
14311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
14321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
14331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
14341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
14351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
14361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
14371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
14381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
14391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
14401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
14421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
14431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
14441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
14451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
14461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1448b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1449b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1450b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1451b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1452b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1453b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1454b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1455b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1456b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1457c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1458b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1459b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip being unselected no longer exists but should.");
1460b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1461c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1462b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14638b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
14648b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
14658b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
14668b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14678b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
14688b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14698b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1471b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1472b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1473b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1474b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1476b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1477c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1479b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1480b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1481b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1482b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1483b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1484b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1485b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1486b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1488b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1489b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1490c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1492b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1493b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1494b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1495b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1496b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1497b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1498b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1499b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1500b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1501b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1502b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1503b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1504b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1505b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
15063656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1507b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1509b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void removeChip(RecipientChip chip) {
1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1512b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1513b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1514b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int toDelete = spanEnd;
1516b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1517b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1518b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Always remove trailing spaces when removing a chip.
15228b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            toDelete++;
1524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        text.delete(spanStart, toDelete);
1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1528b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1530b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1531c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1532b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1533b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1542b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1545b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1547b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1549b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1550b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // There may be a space to replace with this chip's new associated
1551b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // space. Check for it.
1552b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            int toReplace = end;
1553b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            while (toReplace >= 0 && toReplace < editable.length()
1554b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                    && editable.charAt(toReplace) == ' ') {
1555b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                toReplace++;
1556b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1557b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            editable.replace(start, toReplace, chipText);
1558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1559b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1561b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1566b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1567b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1568b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1569b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1576c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1577c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1578b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1580368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1581368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1582368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1583368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1584311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1585311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1586311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1587311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1588311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1589311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1590e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1591e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1592e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
15931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
15941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
15951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
15961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
15971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
15981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
15991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
16001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
16011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
16021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
16041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
16051e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16061e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
16071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
16081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
16091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1610368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1611e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1612e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1613e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mSelectedChip != null) {
1614e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1615e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1616e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1617e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1618e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1619e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1620054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1621e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1622054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
162376ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1624054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1625054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1626054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1627054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1628054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1629054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1630e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1631e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1632e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1633e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1634e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1635e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1636e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1637e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1638e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
1639e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    if (mValidator != null && mValidator.isValid(sub)) {
1640e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1641e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1642e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1643e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1644e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1645e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1646e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1647e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
16481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1649e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1650e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1651e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1652e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1653e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1654e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
165577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
165677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
165777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
165877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String displayText = entry.getDestination();
165977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            displayText = (String) mTokenizer.terminateToken(displayText);
166077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
166177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
166277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
166377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
166477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
166577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
166677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
166777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
166877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
166977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
167077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
167177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
167277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
167377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
167477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
167577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
167677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
167777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            RecipientChip[] existingChips = getSpannable().getSpans(0, getText().length(),
167877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    RecipientChip.class);
167977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
168077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
168177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
168277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
168377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
168477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
168577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
168677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
168777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
168877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
168977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
169077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
169177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
169277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
169377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
16941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
169577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
169677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
16981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
169977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
170077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
170177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
170277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
170377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
170477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
170577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
170677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
170777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
170877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
170977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
171077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
171377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
171477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
171577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
171677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
171777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1718b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1719b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1720b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
1721b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.setSpan(replacements.get(i), start, end,
1722b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
172577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
172677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
173277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
173477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
173577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
173677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
173777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
173877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
174177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
174277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
174377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
174477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
174577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
174677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
174777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
174877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
174977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
175077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
175177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
175277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
17531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
175477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
17571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
175877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
175977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
176077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
176177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
176277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
1771b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1772b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1773b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
1774b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1775b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1776b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1777b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1778b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1779b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1780b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1781b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1782b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1783b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1784b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
1785b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
1786b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
1787b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1788b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
1789b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
1790b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
1791b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
1792b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
1793b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
1794b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
1795b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1796b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1797b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1798b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
1799b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
1800b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = new Dialog(getContext());
1801b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
1802b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
1803b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
1804b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
1805b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
1806b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
1807b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
1808b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1809b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1810b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1811b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1812b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1813b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1814b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1815b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1816b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1817b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
1818b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1819b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1820b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1821b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1822b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
1823b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1824b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1825b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1826b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1827b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1828b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
1830b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = null;
1831b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1832b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1833b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1834b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
1835b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
1836b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
1837b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
1838b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
1839b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
1840b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
184177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
1842