RecipientEditTextView.java revision 00be4ce28569ae7d1871a2f95453b0e6a6a47852
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;
2149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereiraimport android.content.ClipDescription;
22b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager;
232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context;
24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface;
25b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener;
26c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap;
271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory;
28c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix;
30c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
3477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask;
35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler;
36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message;
3722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable;
382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
39572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
47c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
53c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
55a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
56b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
57c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
58c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
62c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
63572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
64416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
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
9300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
9400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
9500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
9600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
9700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
9800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
10200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
10300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final long DISMISS_DELAY = 300;
10400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
1054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
1064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
1074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
10800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int sSelectedTextColor = -1;
10900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
11000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Resources for displaying chips.
111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mInvalidChipBackground;
11600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
11700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mChipBackgroundPressed;
11800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
11900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipHeight;
12000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipFontSize;
12200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Validator mValidator;
128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
137c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
139054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
140a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
141156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
142156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
143c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
144c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
15077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
154bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
166a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
17100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
172e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
173e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
17400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Obtain the enclosing scroll view, if it exists, so that the view can be
17500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // scrolled to show the last line of chips content.
176416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
177416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
17800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private boolean mTriedGettingScrollView;
179416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
186e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
187e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
188e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
189e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
19077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
19177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
197a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
198a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
199a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
200a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
201a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    private Runnable mDelayedShrink = new Runnable() {
202a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
203a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        @Override
204a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        public void run() {
205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            shrink();
206a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        }
207a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
208a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    };
209a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
2102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
21277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
21377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
21477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
215b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
216e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
218e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2190436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
221b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
222b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
224368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
225e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
226b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
229368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
235c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
237156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
238156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
239156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
240156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
243156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
244156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
245156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
246156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
249b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
250ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
251cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
25200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ RecipientChip getLastChip() {
25300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = null;
25400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
25500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null && chips.length > 0) {
25600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            last = chips[chips.length - 1];
257ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
25800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return last;
2594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
26500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = getLastChip();
26600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (last != null) {
26700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Grab the last chip and set the cursor to after it.
26800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
269d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
270d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
271d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
272d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
27322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    @Override
27422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    public void onRestoreInstanceState(Parcelable state) {
27522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (!TextUtils.isEmpty(getText())) {
27622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(null);
27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        } else {
27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(state);
27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
28122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
28200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    @Override
2837c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    public Parcelable onSaveInstanceState() {
2847c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
2857c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        clearSelectedChip();
2867c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        return super.onSaveInstanceState();
2877c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    }
2887c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira
289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
291c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
292c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
295c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
296c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
297001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
298001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
299001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
300001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
3034f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
30405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
307c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
308a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
311a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
312a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
313a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
314a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
3156ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
316c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
317c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
318d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
319d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
320b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
321d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
3224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
3234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
3244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
325416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
3264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
329b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
330b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
331b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
332b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
333b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3355a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3365a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
339a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            if (getWidth() <= 0) {
340a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
341a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // and there is no reason to attempt to commit chips yet.
342a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // This focus lost must be the result of an orientation change
343a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // or an initial rendering.
344a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // Re-post the shrink for later.
345a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
346a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.post(mDelayedShrink);
347a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                return;
348a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
349001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
350001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
351001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
352a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
353001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
354001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
355001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
356001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    Editable text = getText();
360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
361a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    // This token was already tokenized, so skip past the ending token.
3628a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                        whatEnd++;
364a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    }
365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
366001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
367a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int selEnd = getSelectionEnd();
368a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) {
369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
371001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
372001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
373001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
374090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
375001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
38577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
38677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
38777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
38877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
38977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
39077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
394e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
395c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
396c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
397c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
398c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
399c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
402e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
40800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
42377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
42497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
42597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
42697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
428f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
429f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
430f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
431f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
432f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
433f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
442e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
446e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
44800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4501a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4511a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
459045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
460045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
461045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
462045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
46790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
46890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
46990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
47090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
47190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
47290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
47390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
47490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4769024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4779024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4789024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
484ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                if (photo != null) {
485ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
486ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Rect backgroundPadding = new Rect();
487ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    mChipBackground.getPadding(backgroundPadding);
488ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
489ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            0 + backgroundPadding.top,
490ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            width - backgroundPadding.right,
491ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            height - backgroundPadding.bottom);
492ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Matrix matrix = new Matrix();
493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                }
496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4979024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4989024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
500e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
50197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
502379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
50397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
5051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
5071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
51000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /**
51100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     * Get the background drawable for a RecipientChip.
51200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     */
51300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
51400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ Drawable getChipBackground(RecipientEntry contact) {
51500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
51600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                mChipBackground : mInvalidChipBackground;
51700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
51800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
51997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
52097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
52197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        paint.getTextBounds((String)text, 0, text.length(), bounds);
52297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
52397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
52497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
52597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
52600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
5291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
531c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
533c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
53677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
540e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
541c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
543045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
54977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
55277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
557045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
558045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5595519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5605519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
562c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5635519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
564416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
56597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
56697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
567f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
568f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5794f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5804f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5814f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5834f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5861426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5881426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
590b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
591b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
59343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
594045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
595d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
596b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
597b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
610a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
611a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
612a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        mMoreItem = moreItem;
613a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    }
614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
615ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
616ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
617ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
618ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipBackground = chipBackground;
619ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
620ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
621ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
622ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipHeight(int height) {
623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipHeight = height;
624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
626bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
628bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
629bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
631bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
632bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
633bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
638c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
639c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
64053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (width != 0 && height != 0) {
64153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            if (mPendingChipsCount > 0) {
64253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                postHandlePendingChips();
64353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            } else {
64453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                checkChipWidths();
64553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
6467bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
64777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
64800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
649416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
650416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
651416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
652416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
653416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
654416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
655416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
65600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            mTriedGettingScrollView = true;
657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
658c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
659c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
660a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
661a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
662a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
663a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
664a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
66553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void checkChipWidths() {
66653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        // Check the widths of the associated chips.
66753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
66853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (chips != null) {
66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            Rect bounds;
67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            for (RecipientChip chip : chips) {
67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                bounds = chip.getDrawable().getBounds();
67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    // Need to redraw that chip.
67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    replaceChip(chip, chip.getEntry());
67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                }
67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
677a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    }
67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void handlePendingChips() {
6817bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
6827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
6837bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
6847bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
6857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
6867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
68753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (mPendingChipsCount <= 0) {
68953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            return;
69053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        }
69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
692a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
694a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
695a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
7070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
7090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
71000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            sanitizeEnd();
71122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
714a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
715a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
719a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
72177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
722a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
725a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
72622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
727a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
72877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
72977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
730a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
7320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
73800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
73900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void sanitizeEnd() {
7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
74100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
74522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
74900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                lastSpan = getLastChip();
7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
7701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
7711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
7721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
7731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
7741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
775b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
7761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
7780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
781d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
78200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            String destText = createAddressText(entry);
783d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
784b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            int textLength = destText.length() - 1;
785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
786d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
788d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
792d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
79822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
79922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
80022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
8016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
803d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
80477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
8050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
8070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
808d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
809d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
810d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
8110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
8121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
8136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
814454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
815454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
8171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
8181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
8196ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
8206ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
8216ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
8226ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
8236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
8241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
8251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
8266ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
8276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
828a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
8296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
831a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
833a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
834a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
837a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
838a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
845490556a764a879cd0eaff358e90705cc1335c92eErik                }
846d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
8476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
848454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
849a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
850a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
8511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
8521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
8536ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
8546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
8566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
8571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
8581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
8591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
8601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
8610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
8630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
865c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
866c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
869c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
882c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
883c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
884c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
885c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
886c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
89195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
89295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
89395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
89495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
89595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
89695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
89795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
8988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
90695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
907c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
908c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
909c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
912d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
915e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
916e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
917e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
918e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
919e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
92295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
926e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
927e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
929e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
939e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
947045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
948045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
949045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
950045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
951045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
952045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
953045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
9548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
9554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
9574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
958dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
959e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
961e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
9664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
9784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
979054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
9814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
98349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        ListAdapter adapter = getAdapter();
98449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
98549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                && end == getSelectionEnd()) {
986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
989e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
99249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (editable.length() > tokenEnd + 1) {
99349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
99449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
99549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd++;
99649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
997a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
998b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
1000e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
10011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1002d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
1003d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1004d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
100571fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
10065ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
10075ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
1008d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
100949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
101049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // just committed.
101149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // For paste, it may not be as there are possibly multiple
101249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // tokens being added.
101349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (end == getSelectionEnd()) {
101449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    dismissDropDown();
101549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1016f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
10174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
1018d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
1019d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
10204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
1021d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
1022d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
10234031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10244031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
1025f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
10264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
1027f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
1028f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
1029f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
1030f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
1034f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1037c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
10384031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1039c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
1040c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
1041c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
1042c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
1043f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
1044f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
1045f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1046a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking != end) {
1047f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
1048f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1049f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
1050f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
1052e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
10531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
10541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
10551e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
10561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
10571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
10581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
10591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
106005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
1062e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1063e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1064e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
106539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
106639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
106739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
106839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
106939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
1070e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
1071e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
1072e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1073e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
1074e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
10753b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
10763b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
10773b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
10783b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
1079a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (chipText != null) {
1080a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.replace(start, getSelectionEnd(), chipText);
1081a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            }
10823b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
1083054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
108405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
108505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
10868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
10888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
10898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1093b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1094b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
11042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
11064031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
11074031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1108ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1111b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1112b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1113b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1114b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1117b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
112849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (enoughToFilter() && !isCompletedToken(text)) {
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
114249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
114349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
114449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (TextUtils.isEmpty(text)) {
114549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return false;
114649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
114749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Check to see if this is a completed token before filtering.
114849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int end = text.length();
114949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
115049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String token = text.toString().substring(start, end).trim();
115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (!TextUtils.isEmpty(token)) {
115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return false;
115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
116336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
11688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
11698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
11718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
11728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
11738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1176d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1177d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1178d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1179d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1181d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
11836caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
11860436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1195b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
11978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
11988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1205416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
12065753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
12075753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
12085753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1217416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1218416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1219c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1220416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1221416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1222416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1226c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1230e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
123177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1232416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1234368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1235e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1236e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1244b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1250e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1251e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1252e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1253e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1254e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1255e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1258b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1259b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1260b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
12611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
12621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
12631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
12641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
12651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1266a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1270b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1272e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1276c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1277c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1278c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12924fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
12934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
12944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
12974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1298c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1301c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
13064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
13074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
13084fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
13094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
13104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
13114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1313c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1317b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1318b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1319b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
132700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to add to the list of addresses.
132800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createAddressText(RecipientEntry entry) {
13298659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
13308659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
13318659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
13328659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
13338659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1334a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1335a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1336a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1337c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1338c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1339c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1340c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1341a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
13428659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
134300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String trimmedDisplayText = token.toString().trim();
13448659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
13458659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
134600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
134700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
134800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
134900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
135000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to display in a chip.
135100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
135200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String display = entry.getDisplayName();
135300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String address = entry.getDestination();
135400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
135500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            display = null;
135600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
135700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (address != null) {
135800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Tokenize out the address in case the address already
135900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // contained the username as well.
136000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
136100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
136200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                address = tokenized[0].getAddress();
136300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
136400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
136500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (!TextUtils.isEmpty(display)) {
136600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return display;
136700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
136800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return address;
136900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else {
137000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return new Rfc822Token(display, address, null).toString();
137100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
13728659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
13738659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1374fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String displayText = createAddressText(entry);
1376a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
1377a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            return null;
1378a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        }
1379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1380b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int textLength = displayText.length()-1;
1381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1382c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1384c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
13856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
13866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
13886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
13998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
14008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1402c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1406c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
14070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
14080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
14091e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
14101e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
14111e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
14194221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
14204221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
14214221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
14224221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1423f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
14270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
14280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
14290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
14310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
14320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
14330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
14340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1435c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1436c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
14370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
14390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
14400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
14410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
14430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
14440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
144800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
14497a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
14507a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
14517a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
14527a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1453c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1454c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
145700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
145800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
145900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */ Collection<Long> getDataIds() {
146000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        final Set<Long> result = new HashSet<Long>();
146100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip [] chips = getSortedRecipients();
146200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null) {
146300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            for (RecipientChip chip : chips) {
146400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                result.add(chip.getDataId());
146500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
146600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
146700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return result;
146883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
146983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
14704031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
147100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */RecipientChip[] getSortedRecipients() {
147200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] recips = getSpannable()
147300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .getSpans(0, getText().length(), RecipientChip.class);
14746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
147500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .asList(recips));
14766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
14776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
14786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
14796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
14806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
14816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
14826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
14836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
14846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
14856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
14866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
14876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
14886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
14896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
14916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
14926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
14946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
14954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
14974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
14984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
15024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
15064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
15108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
15118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
15144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1517a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1518a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ImageSpan getMoreChip() {
151922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
152022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
152122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
152222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
152322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
15248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1525045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1526045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1527045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
15288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1529a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1530a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void createMoreChip() {
1531bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1532bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1533bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1534bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
15356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
15366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
15376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
15386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
15396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
154083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
15410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
15446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
154583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
15464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1547c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1548c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1549c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1550c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1551c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1552c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
15534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
15544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
15554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
155622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        int adjustedHeight = height;
155722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        Layout layout = getLayout();
155822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (layout != null) {
155922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            adjustedHeight -= layout.getLineDescent(0);
156022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
156122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
15624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
15644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
15656f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
15664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1567368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
15690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
15700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1573368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
15744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
15754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
15766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1577368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1578368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
15799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1580368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
15819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1582368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1583368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
15849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
15856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
15866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
15876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
15886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
158977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1590368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
15914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
159277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
159377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
159477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
15954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
159677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
15970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
15984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
16008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
16018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
16028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
16038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1604a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1605a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /*package*/ void removeMoreChip() {
16064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
16074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
16084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
16094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
16104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
16114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
16124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1613c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
161464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
161564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1616c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1617c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1618c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
161964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
16204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
16214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
16226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
16230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
16240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
16256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
16266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
162764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
162864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
162964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
163064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
163164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
163264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1633bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1634bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1635bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1636bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1637bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
16384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
16394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
16404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
16414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1645b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1646b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1647b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1648b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1649b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1653b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
165600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip selectChip(RecipientChip currentChip) {
16571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
16581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
16591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
16601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
16611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
16621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
16642f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
16651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1666b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1667b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1668b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1669b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1670b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1671b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1672b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1673b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1675b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1676fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
167883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
168083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
16818b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
168283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
16841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
16851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1686c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
16871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
16886ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1689b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1690b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
16911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
16921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
16931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
16941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
16951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
16961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
16981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
16991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
17001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1701b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
17021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
17061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
17111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
17136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1715fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1716b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1717fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1718c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
17201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
17211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
17221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
17231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
17241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1725e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
17261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
17271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
17281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
17291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
17301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
17311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
17351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
17391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
17401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
17411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
17421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1743b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1744b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1745b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1746b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
174800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private void unselectChip(RecipientChip chip) {
1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1752c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
17545753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            Log.w(TAG,
17555753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    "The chip doesn't exist or may be a chip a user was editing");
17565753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
17575753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1759c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17618b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
17628b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
17638b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
17648b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17658b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
17668b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17678b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1768c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1769b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1771b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1772b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1773c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1774b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1775c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1778b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1785b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1786b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
17903656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1794ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
1795ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
1796b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1797b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1798b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1799b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1800b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int toDelete = spanEnd;
1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1802b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1804b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1806b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        // Always remove trailing spaces when removing a chip.
1807b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1808b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            toDelete++;
1809b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        }
1810b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1811b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        text.delete(spanStart, toDelete);
1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1815b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1817b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1819b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
182100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
182200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) {
1823b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1824b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1827b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1829b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1830b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1831b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1832a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (chipText != null) {
1833a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (start == -1 || end == -1) {
1834a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1835a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.insert(0, chipText);
1836a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            } else {
1837a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
1838a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // There may be a space to replace with this chip's new
1839a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // associated
1840a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // space. Check for it
1841a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    int toReplace = end;
1842a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
1843a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
1844a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                        toReplace++;
1845a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    }
1846a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    editable.replace(start, toReplace, chipText);
1847b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                }
1848b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1850b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1851b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1852b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1853c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1854b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1855c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1857b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1858b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1860b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1863b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1865b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1866b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1871368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1872368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1873368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1874368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1875311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1876311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1877311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1878311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1879311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1880311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1881e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1883e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
18841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
18851e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
18861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
18871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
18881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
18891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
18901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
18911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
18921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
18931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
18941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
18951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
18961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
18971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
18981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
18991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
19001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1901368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
19045753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
19055753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
19065753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
1907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1909e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1911e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1913054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1915054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
191676ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1917054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1918054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1919054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1920054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1921054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1922054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1923e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1924e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1925e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1926e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1927e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1928e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1929e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1930e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1931e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
19323b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1936e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1937e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1939e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1940e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
19411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1942e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1943e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
194622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
1947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
194977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
195049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    @Override
195149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    public boolean onTextContextMenuItem(int id) {
195249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (id == android.R.id.paste) {
195349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            removeTextChangedListener(mTextWatcher);
195449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
195549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    Context.CLIPBOARD_SERVICE);
195649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipData clip = clipboard.getPrimaryClip();
195749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (clip != null
195849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
195949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                for (int i = 0; i < clip.getItemCount(); i++) {
196049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    CharSequence paste = clip.getItemAt(i).getText();
196149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    if (paste != null) {
196249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        int start = getSelectionStart();
196349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        int end = getSelectionEnd();
196449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        Editable editable = getText();
196549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        if (start >= 0 && end >= 0 && start != end) {
196649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                            editable.append(paste, start, end);
196749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        } else {
196849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                            editable.insert(end, paste);
196949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        }
197049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        handlePaste();
197149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    }
197249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
197349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
197449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            mHandler.post(mAddTextWatcher);
197549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return true;
197649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
197749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return super.onTextContextMenuItem(id);
197849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
197949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
198049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
198149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */void handlePaste() {
198249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String text = getText().toString();
198349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
198449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
198549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int tokenStart = originalTokenStart;
198649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int prevTokenStart = tokenStart;
198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        RecipientChip findChip = null;
198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenStart != 0) {
198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            // There are things before this!
199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            while (tokenStart != 0 && findChip == null) {
199149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                prevTokenStart = tokenStart;
199249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
199349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                findChip = findChip(tokenStart);
199449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
199549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (tokenStart != originalTokenStart) {
199649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (findChip != null) {
199749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = prevTokenStart;
199849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
199949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                int tokenEnd;
200049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                RecipientChip createdChip;
200149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                while (tokenStart < originalTokenStart) {
200249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
200349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
200449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    createdChip = findChip(tokenStart);
200549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    // +1 for the space at the end.
200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
200749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
200949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Take a look at the last token. If the token has been completed with a
201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // commit character, create a chip.
201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (isCompletedToken(lastAddress)) {
201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            Editable editable = getText();
201449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            commitChip(editable.toString().indexOf(lastAddress, originalTokenStart), editable
201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    .length(), editable);
201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd >= length()) {
202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return tokenEnd;
202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
202649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
202749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
202849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // This token had not only an end token character, but also a space
202949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // separating it from the next token.
203049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
203149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
203249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
203349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return tokenEnd;
203449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
203549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
203677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
203777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
203877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
203977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
204077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
204177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
204277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
204377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
204477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
204577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
204677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
204777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
204877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
204977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
205077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
205177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
205277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
205377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
205477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
20556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
205677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
205777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
205877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
205977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
206077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
206177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
206277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
206377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
206400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                addresses[i] = createAddressText(originalRecipients.get(i).getEntry());
206577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
20711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
20741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
20751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
208077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
208577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
208677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
208777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
208877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
208977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
209077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
209177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
209477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
2095b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
20962e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                                oldText.removeSpan(chip);
2097b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
20986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
20996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
2100b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
21016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
210477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
210577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
21062e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                        setText(text);
210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
211577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
212077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
212177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
212277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
212377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
212477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
212500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                addresses[i] = createAddressText(originalRecipients.get(i).getEntry());
212677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
212777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
212877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
212977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
21301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
213177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
21331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
21341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
213677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
213877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
213977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
214077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
2148b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
21496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
21506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
21516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
21526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
21536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
21546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
21556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
21566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
21576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
21586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
21596f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
2160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
2162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2166b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2167b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2168b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2169b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2170b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2171b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2172b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
2173b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
2174b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
2175b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2176b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
2177b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
2178b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
2179b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
2180b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
2181b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
2182b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
2183b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2186b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
2187b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
2188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
2189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2206b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2207b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2208b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2209b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2210b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2211b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2212b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2213b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2214b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2215b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2216b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2217b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2218b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2219b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2220b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2221b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2222b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2223b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2224b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2225b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2226b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
222777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2228