RecipientEditTextView.java revision 4031c89f46edea42c64e183e46d49484f1f203c0
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;
3622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable;
372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
38572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
46c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
512d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
52c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
54a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
55b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
56c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
57c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
62572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
63416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
66cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable;
67b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
69156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
71e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow;
72416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
73c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
74b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
75a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
77b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
8077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
88b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
89b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
90e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
91e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        PopupWindow.OnDismissListener {
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
116c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
118054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
119a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
123e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
126045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
127045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
133156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
134c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
135c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
13677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
14005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
141c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
142e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
143dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
152bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
164a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
175416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
1972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
19977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
20077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
20177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
202b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
203e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
205e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2060436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
211368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
212e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
215b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
216368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
222c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
227156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
229156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
230156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
231156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
232156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
233156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
236b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
237ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
238cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
239ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
240ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
241ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
242ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
243ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
244ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2464fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2504fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
25183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
25283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
25383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
25483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
259d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
260d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
261d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
262d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
26322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    @Override
26422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    public void onRestoreInstanceState(Parcelable state) {
26522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (!TextUtils.isEmpty(getText())) {
26622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(null);
26722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        } else {
26822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(state);
26922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
27022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
27122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
272c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
273c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
275c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
276c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
277c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
278c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
279c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
280001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
281001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
282001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
283001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
284c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
285c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
2864f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
28705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
288c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
291a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
292c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
294a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
295a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
296a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
297a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
2986ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
300c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
301d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
302d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
303b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
304d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
3054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
3064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
3074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
308416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
3094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
312b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
313b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
314b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
315b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
316b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3185a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3195a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
324001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
325a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
326001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
327001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
328001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
329001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
330001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
331001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
332001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(getText(), start);
333001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
334001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
335001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    if (whatEnd != getSelectionEnd()) {
336001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
337001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
338001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
339001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
340001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
341090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
342001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
35277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
35377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
35477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
35577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
35677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
35777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
361e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
362c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
363c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
364c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
365c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
366c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
369e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
373e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
3761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
3771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
3801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
3811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
3821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
3851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
3871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
3881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
3891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
39077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
39197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
39297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
39397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
3941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
395f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
396f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
397f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
398f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
399f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
400f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
408045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
409045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
410045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
411045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
412a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
413045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
414045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
415045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
416e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
419c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
420e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
4224221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
4234221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
4244221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
4254221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4271a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4281a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
435c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
436045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
437045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
438045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
439045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
44490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
44590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
44690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
44790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
44890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
44990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
45090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
45190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
45290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4549024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4559024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4599024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
4619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
4629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
463f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                Rect backgroundPadding = new Rect();
464f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                mChipBackground.getPadding(backgroundPadding);
465f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
466f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                        0 + backgroundPadding.top,
467f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                        width - backgroundPadding.right,
468f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                        height - backgroundPadding.bottom);
469f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
4709024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
475e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
47697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
477379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
47897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
4801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
48597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
48697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
48797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        paint.getTextBounds((String)text, 0, text.length(), bounds);
48897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
48997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
49097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
49197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
4921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
4931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
4941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
4951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
4961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
499c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
50277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
506e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
509045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
51577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
51877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
523045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
524045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5255519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5265519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
528c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5295519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
530416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
53197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
53297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
533f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
534f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5454f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5464f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5474f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5494f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5521426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5541426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
556b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
557b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
55943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
560045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
561d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
562b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
563b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
564b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
565b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
566b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
567b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
568b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
569b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
570b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
571b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
572b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
573b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
574b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
575b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
576bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
577bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
578bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
579bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
580bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
581bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
582bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
583bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
584bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
585bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
586bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
588c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
589c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
5907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (width != 0 && height != 0 && mPendingChipsCount > 0) {
5917bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            postHandlePendingChips();
5927bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
59377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
594416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
595416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
596416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
597416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
598416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
599416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
600416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
601416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
602416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
603416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
604c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
605c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
6110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void handlePendingChips() {
612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount <= 0) {
613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            return;
614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
6157bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
6167bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
6177bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
6187bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
6197bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
6207bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
625a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
626a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
629a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
630a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
631a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
632a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
633a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
634a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
635a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
6360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
637a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
639a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
64022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
641a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
642a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
643a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
644a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
645a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
646a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
647a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
648a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
649a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
65077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
651a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
652a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
653a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
654a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
65522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
656a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
65777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
65877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
659a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
660a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
6710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
67322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
6740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
6750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
6760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
6770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
6780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
6800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
6810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
6820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
6830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
6840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
6850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
6860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
6870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
6880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
6890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
6950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
6960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
6981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
6991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
7001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
7011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
7021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
703b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
7041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
7050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
7060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
7070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
709d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
710a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            String destText = createDisplayText(entry);
711d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
712d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int textLength = destText.length() - 1;
713d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
714d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
715d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
716d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
717d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
718d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
719d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
720d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
721d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
722d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
723d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
724d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
725d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
72622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
72722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
72822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
7296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
730d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
731d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
73277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
736d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
737d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
738d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
7401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
7416ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
742454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
743454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
7441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
7451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
7461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
7476ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
7486ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
7496ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
7506ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
7516ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
7521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
7531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
7546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
7556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
756a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
7576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
7586ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
759a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
760a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
761a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
762a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
763a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
764a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
765a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
766a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
767a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
768a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
769a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
770a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
771a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
772a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
773490556a764a879cd0eaff358e90705cc1335c92eErik                }
774d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7756ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
776454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
777a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
778a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
7791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
7801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
7816ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
7826ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
7836ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
7846ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
7851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
7861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
7871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
7881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
7890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
7910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
793c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
794c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
795c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
797c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
798c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
8008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
8018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
8028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
8038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
8048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
8078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
8088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
8098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
812c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
8178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
818c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
81995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
82095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
82195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
82295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
82395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
82495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
82595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
8268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
8288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
8298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
8308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
8318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
8328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
8338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
83495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
835c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
837c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
838c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
840d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
841c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
842c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
843e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
844e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
845e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
846e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
847e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
848e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
85095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
851e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
852e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
853e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
854e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
855e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
856e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
857e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
858e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
859e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
860e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
861e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
862c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
863c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
864c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
866e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
867e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
868e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
869e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
870e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
871e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
872e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
873e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
874e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
875045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
876045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
877045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
878045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
879045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
880045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
881045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
8834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
884045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
8854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
886dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
889e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
890e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
8944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
895e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
896e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
897e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
899e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
905e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
9064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
907054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
9094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
911001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
9211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
922d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
923d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
924d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
9255ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    if (chipText != null) {
9265ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
9275ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
928d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
929d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
930f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
9314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
932d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
933d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
9344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
935d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
936d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
9374031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
9384031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
939f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
9404031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
941f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
942f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
943f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
944f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
945f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
946f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
947f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
948f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
949f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
950f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
951c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
9524031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
953c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
954c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
955c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
956c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
957f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
958f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
959f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
960f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (startLooking != end) {
961f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
962f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
963f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
964f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
965f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
966e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
9671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
9681e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
9691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
9701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
9711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
9721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
9731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
97405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
9751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
97939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
98039f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
98139f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
98239f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
98339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
985e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
9893b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
9903b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
9913b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
9923b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
9933b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            editable.replace(start, getSelectionEnd(), chipText);
9943b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
995054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
99605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
99705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
9988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
10008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
10018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1004b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1005b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1006b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1007b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1008b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1009c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1011c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
10162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
10172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
10184031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10194031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1020ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1022c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1023b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1024b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1025b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1026b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1027b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1028b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1029b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1030b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1031c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1032c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1033c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1036c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1037c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1038c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1039c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
1040c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
1041c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1042c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1043c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1044c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1045c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1046c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1047c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1048c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1049c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1050c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1051c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1052c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1053c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1054c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1055c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1056b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1057c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1058c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
105936d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1060c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1061c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
10638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
10648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
10658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
10668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
10678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
10688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
10698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1070c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1071c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1072d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1073d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1074d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1075d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1077d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
10796caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1080b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1081b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
10820436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1091b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
10938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
10948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1097b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1101416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
11025753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
11035753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
11045753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1113416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1114416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1115c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1116416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1117416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1118416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1119b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1120b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1121b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1122c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1126e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
112777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1128416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1129b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1130368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1131e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1132e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1133b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1134b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1135b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1136b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1137b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1138b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1139b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1140b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1141b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1142b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1146e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1147e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1148e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1149e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1150e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1151e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1152b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1153b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1154b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1155b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1156b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
11571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
11581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
11591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
11601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
11611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1162a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1168e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1170b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1181c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1185c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1186c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11884fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
11894fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
11904fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
11934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1194c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1195c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1199c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
12024fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
12034fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
12044fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
12054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
12064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
12074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1208c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1209c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1215b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1217c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12224031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
12234031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ String createDisplayText(RecipientEntry entry) {
12248659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
12258659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
12268659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
12278659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
12288659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1229a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1230a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1231a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1232c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1233c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1234c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1235c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1236a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
12378659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
12388659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String displayText = token.toString();
12398659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String trimmedDisplayText = displayText.trim();
12408659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
12418659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
12428659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                .terminateToken(displayText) : displayText;
12438659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
12448659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1245fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
12468659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String displayText = createDisplayText(entry);
1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1248b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        int textLength = displayText.length()-1;
1249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1251c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
12536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
12546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1255c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
12566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1257c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1260c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1261c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1264c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
12678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
12688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1269c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1270c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1272c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1273c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1274c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
12750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
12760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
12771e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
12781e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
12791e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
12874221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
12884221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
12894221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
12904221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1291f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1292c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1293c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
12950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
12960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
12970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
12980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
12990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
13000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
13010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
13020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1303c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1304c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
13050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
13060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
13070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
13080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
13090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
13100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
13110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
13120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
13167a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
13177a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
13187a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
13197a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
13207a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
132583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
132683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
132783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
132883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
13294031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
13304031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ RecipientChip[] getSortedRecipients() {
13316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
13326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                .asList(getRecipients()));
13336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
13346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
13356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
13366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
13376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
13386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
13396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
13406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
13416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
13426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
13436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
13446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
13456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
13466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
13476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
13486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
13496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
13506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
13516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
135583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
13567a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
13577a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
13587a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
13597a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1361c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1362c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1363c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
13664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
13674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
13684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
13714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
13724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
13754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
13764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
13774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
13818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
13834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
13844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
13854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
138722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
138822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    private ImageSpan getMoreChip() {
138922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
139022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
139122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
139222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
139322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
13948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1395045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1396045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1397045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
13988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createMoreChip() {
1400bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1401bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1402bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1403bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
14046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
14056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
14066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
14076f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
14086f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
140983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
14100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
14110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
14124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
14136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
141483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
14154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1416c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1417c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1418c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1419c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1420c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1421c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
14224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
14234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
14244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
142522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        int adjustedHeight = height;
142622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        Layout layout = getLayout();
142722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (layout != null) {
142822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            adjustedHeight -= layout.getLineDescent(0);
142922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
143022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
14314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
14334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
14346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
14354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1436368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
14374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
14390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
14400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
14414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1442368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
14434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
14444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
14456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1446368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1447368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
14489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1449368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
14509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1451368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1452368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
14539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
14546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
14556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
14566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
14576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
145877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1459368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
14604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
146177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
146277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
146377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
14644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
146577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
14660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
14674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
14708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
14718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
14728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
14734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
14744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
14754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
14764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
14774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
14784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
14794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
14804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1481c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
148264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
148364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1484c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1485c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1486c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
148764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
14884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
14894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
14910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
14920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
14946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
149564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
149664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
149764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
149864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
149964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
15006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // -1 for the space!
150164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1502bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1503bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1504bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1505bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1506bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
15074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
15084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
15094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
15104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
15114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1514b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1516b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1517b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1518b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1522b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
15261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
15271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
15281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
15291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
15301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
15311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
15321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
15332f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
15341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1540b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1542b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1545fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
154783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
154983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
15508b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
155183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
15531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
15541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1555c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
15561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
15576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1558b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1559b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
15601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
15611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
15621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
15631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
15641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
15651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
15661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
15671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
15681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
15691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
15711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
15721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
15731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
15741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
15751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
15761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
15771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
15781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
15791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
15801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
15811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
15826ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
15831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1584fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1586fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
15881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
15891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
15901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
15911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
15921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
15931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1594e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
15951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
15961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
15971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
15981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
15991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
16001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
16011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
16021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
16031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
16041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
16051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
16061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
16071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
16081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
16091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
16101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
16111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1612b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1613b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1614b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1615b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1616b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1617b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1618b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1619b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1620b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1621c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1622b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
16235753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            Log.w(TAG,
16245753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    "The chip doesn't exist or may be a chip a user was editing");
16255753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
16265753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1627b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1628c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1629b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
16308b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
16318b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
16328b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
16338b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
16348b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
16358b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
16368b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1638b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1639b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1640b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1641b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1642c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1643b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1645b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1646b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1647b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1648b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1649b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1653c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1655b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1656c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1657c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1658b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1659b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1660b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1661b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1662b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1663b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1664b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1665b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1666b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1667b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1668b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1669b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1670b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1671b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
16723656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1673b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1675b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1676b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void removeChip(RecipientChip chip) {
1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1680b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1681b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int toDelete = spanEnd;
1682b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1684b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1685b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1686c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1687b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Always remove trailing spaces when removing a chip.
16888b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1689b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            toDelete++;
1690c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1691b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1692b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        text.delete(spanStart, toDelete);
1693b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1694b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1695c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1696b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1697c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1698b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1699b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1700b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1701b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1702b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1703b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1704b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1705b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1706c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1707b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1708b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1709b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1710b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1711b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1712b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1713b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1714b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1715b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1716b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // There may be a space to replace with this chip's new associated
1717b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // space. Check for it.
1718b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            int toReplace = end;
1719b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            while (toReplace >= 0 && toReplace < editable.length()
1720b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                    && editable.charAt(toReplace) == ' ') {
1721b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                toReplace++;
1722b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1723b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            editable.replace(start, toReplace, chipText);
1724c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1727b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1728c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1729b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1730c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1731b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1732b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1733b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1734b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1735b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1736b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1737b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1738b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1739b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1740b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1741b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1742c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1743c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1744b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1745c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1746368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1747368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1748368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1749368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1750311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1751311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1752311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1753311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1754311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1755311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1756e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1757e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1758e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
17591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
17601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
17611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
17621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
17631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
17641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
17651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
17661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
17671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
17681e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
17691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
17701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
17711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
17721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
17731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
17741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
17751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1776368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1777e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1778e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
17795753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
17805753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
17815753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
1782e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1783e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1784e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1785e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1786e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1787e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1788054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1789e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1790054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
179176ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1792054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1793054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1794054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1795054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1796054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1797054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1798e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1799e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1800e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1801e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1802e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1803e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1804e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1805e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1806e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
18073b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
1808e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1809e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1810e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1811e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1812e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1813e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1814e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1815e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
18161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1817e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1818e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1819e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1820e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
182122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
1822e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1823e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
182477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
182577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
182677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
182777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
182877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
182977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
183077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
183177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
183277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
183377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
183477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
183577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
183677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
183777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
183877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
183977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
184077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
184177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
184277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
184377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
18446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
184577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
184677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
184777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
184877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
184977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
185077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
185177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
185277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
18538659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                addresses[i] = createDisplayText(originalRecipients.get(i).getEntry());
185477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
185577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
185677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
185777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
185877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
185977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
18601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
186177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
186277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
18631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
18641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
186577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
186677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
186777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
186877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
186977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
187077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
187177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
187277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
187377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
187477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
187577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
187677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
187777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
187877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
187977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
188077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
188177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
188277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
188377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1884b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1885b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1886b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
18876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
18886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
1889b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
18906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
189177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
189277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
189377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
189477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
189577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
189677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
189777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
189877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
189977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
190077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
190177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
190277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
190377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
190477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
190577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
190677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
190777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
190877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
190977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
191077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
191177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
191277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
191377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
191477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
191577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
19168659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                addresses[i] = createDisplayText(originalRecipients.get(i).getEntry());
191777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
191877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
191977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
192077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
19211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
192277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
192377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
19241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
19251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
192677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
192777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
192877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
192977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
193077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
193177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
193277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
193377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
193477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
193577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
193677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
193777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
193877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
1939b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
19406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
19416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
19426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
19436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
19446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
19456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
19466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
19476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
19486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
19496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
19506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1951b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1952b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
1953b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1954b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1955b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1956b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1957b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1958b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1959b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1960b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1961b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1962b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1963b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
1964b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
1965b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
1966b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1967b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
1968b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
1969b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
1970b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
1971b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
1972b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
1973b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
1974b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1975b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1976b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1977b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
1978b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
1979b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
1980b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
1981b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
1982b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
1983b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
1984b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
1985b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
1986b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1987b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1988b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1989b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1990b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1991b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1992b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1993b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1994b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1995b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
1996b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1997b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1998b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1999b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2000b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2001b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2002b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2003b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2004b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2005b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2006b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2007b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2008b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2009b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2010b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2011b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2012b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2013b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2014b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2015b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2016b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2017b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
201877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2019