RecipientEditTextView.java revision 3e9631982511d9692e4bc0dbd724240fae91cf1f
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;
303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.graphics.Point;
31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
33c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
3577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask;
36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler;
37156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message;
3822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable;
392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
40572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
48c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
50c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
532d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
54c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
56a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.view.DragEvent;
58b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
60c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
65572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
66416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
69b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
70c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
71156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
73e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow;
74416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
75c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
76b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
77a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
79b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
8277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
90b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
91b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
92e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
93e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        PopupWindow.OnDismissListener {
94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
9600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
9700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
9800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
9900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
10000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
10400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
10500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final long DISMISS_DELAY = 300;
10600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
1074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
1084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
1094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
11000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int sSelectedTextColor = -1;
11100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
11200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Resources for displaying chips.
113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mInvalidChipBackground;
11800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
11900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mChipBackgroundPressed;
12000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipHeight;
12200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipFontSize;
12400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Validator mValidator;
130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
139c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
141054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
142a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
143156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
144156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
145c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
146c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
147b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
148b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
15177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
15277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
155bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
156bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
167a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
168a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
170b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
171b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
17300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
17600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Obtain the enclosing scroll view, if it exists, so that the view can be
17700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // scrolled to show the last line of chips content.
178416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
179416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
18000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private boolean mTriedGettingScrollView;
181416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1823e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private boolean mDragEnabled = false;
1833e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
186e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
187e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
188e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
189e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
190e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
191e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
192e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
193e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
19477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
19577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
197a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
198a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
199a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
200a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
201a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
202a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
203a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
204a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    private Runnable mDelayedShrink = new Runnable() {
206a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
207a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        @Override
208a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        public void run() {
209a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            shrink();
210a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        }
211a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
212a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    };
213a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
2142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
21677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
21777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
21877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
220e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
222e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2230436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
226b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
228368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
229e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
233368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
234b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
235b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
236b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
239c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
241156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
243156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
244156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
246156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
247156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
248156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
249156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
250156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2516ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2526ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
253b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
254ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
255cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
25600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ RecipientChip getLastChip() {
25700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = null;
25800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
25900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null && chips.length > 0) {
26000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            last = chips[chips.length - 1];
261ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
26200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return last;
2634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2664fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2674fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
26900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = getLastChip();
27000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (last != null) {
27100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Grab the last chip and set the cursor to after it.
27200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
273d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
274d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
275d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
276d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    @Override
27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    public void onRestoreInstanceState(Parcelable state) {
27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (!TextUtils.isEmpty(getText())) {
28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(null);
28122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        } else {
28222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(state);
28322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
28422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
28522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
28600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    @Override
2877c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    public Parcelable onSaveInstanceState() {
2887c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
2897c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        clearSelectedChip();
2907c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        return super.onSaveInstanceState();
2917c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    }
2927c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira
293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
295c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
296c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
297c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
298c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
300c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
301001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
302001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
303001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
304001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
3074f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
30805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
311c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
312a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
313c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
314c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
315a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
316a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
317a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
318a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
3196ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
320c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
321c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
322d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
323d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
324b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
325d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
3274fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
3284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
329416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
3304fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
333b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
334b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
335b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
336b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
337b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3395a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3405a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
343a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            if (getWidth() <= 0) {
344a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
345a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // and there is no reason to attempt to commit chips yet.
346a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // This focus lost must be the result of an orientation change
347a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // or an initial rendering.
348a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // Re-post the shrink for later.
349a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
350a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.post(mDelayedShrink);
351a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                return;
352a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
353001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
354001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
355001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
356a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
359001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
360001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
361001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
362001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    Editable text = getText();
364a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
365a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    // This token was already tokenized, so skip past the ending token.
3668a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
367a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                        whatEnd++;
368a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    }
369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
371a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int selEnd = getSelectionEnd();
372a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) {
373001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
374001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
375001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
376001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
377001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
378090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
379001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
38977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
39077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
39177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
39277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
39377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
39477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
398e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
399c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
400c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
401c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
402c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
403c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
410e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
41200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
4251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
42777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
42897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
42997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
43097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
432f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
433f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
434f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
435f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
436f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
437f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
445045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
446e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
450e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
45200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4541a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4551a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
462c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
463045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
464045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
465045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
466045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4689024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4709024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
47190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
47290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
47390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
47490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
47690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
47990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
488ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                if (photo != null) {
489ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
490ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Rect backgroundPadding = new Rect();
491ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    mChipBackground.getPadding(backgroundPadding);
492ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            0 + backgroundPadding.top,
494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            width - backgroundPadding.right,
495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            height - backgroundPadding.bottom);
496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Matrix matrix = new Matrix();
497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                }
500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
5019024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
5029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
504e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
50597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
506379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
50797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
5091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
5111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
5121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
51400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /**
51500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     * Get the background drawable for a RecipientChip.
51600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     */
51700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
51800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ Drawable getChipBackground(RecipientEntry contact) {
51900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
52000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                mChipBackground : mInvalidChipBackground;
52100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
52200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
52397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
52497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
5253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        paint.getTextBounds(text, 0, text.length(), bounds);
52697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
52797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
52897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
52997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
53000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
5321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
5331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
5341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
535c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
537c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
54077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
544e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
547045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
55377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
55677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
561045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
562045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5635519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5645519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
566c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5675519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
568416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
56997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
57097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
571f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
572f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5834f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5844f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5854f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5874f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5901426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5921426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
594b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
595b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
596c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
59743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
598045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
599d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
610b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
611b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
612b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
613b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
615a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
616a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        mMoreItem = moreItem;
617a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    }
618a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
619ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
620ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
621ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
622ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipBackground = chipBackground;
623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
626ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipHeight(int height) {
627ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipHeight = height;
628ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
629ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
631bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
632bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
633bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
637bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
638bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
639bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
640bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
642c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
643c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
64453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (width != 0 && height != 0) {
64553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            if (mPendingChipsCount > 0) {
64653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                postHandlePendingChips();
64753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            } else {
64853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                checkChipWidths();
64953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
6507bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
65177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
65200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
653416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
654416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
655416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
656416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
658416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
659416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
66000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            mTriedGettingScrollView = true;
661416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
662c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
663c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
664a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
665a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
666a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
667a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
668a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void checkChipWidths() {
67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        // Check the widths of the associated chips.
67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (chips != null) {
67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            Rect bounds;
67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            for (RecipientChip chip : chips) {
67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                bounds = chip.getDrawable().getBounds();
67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
67753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    // Need to redraw that chip.
67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    replaceChip(chip, chip.getEntry());
67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                }
68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
681a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
68253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    }
68353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
68453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void handlePendingChips() {
6857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
6867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
6877bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
6887bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
6897bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
6907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
69253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (mPendingChipsCount <= 0) {
69353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            return;
69453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        }
69553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
710a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
7110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
7130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
71400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            sanitizeEnd();
71522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
719a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
721a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
722a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
72577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
726a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
727a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
728a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
729a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
73022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
73277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
73377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
734a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
735a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
74200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
74300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void sanitizeEnd() {
7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
74500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
74922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
75300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                lastSpan = getLastChip();
7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
7710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
7720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
7741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
7751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
7761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
7771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
7781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
779b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
7801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
7810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
7820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
7830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
78600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            String destText = createAddressText(entry);
787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
788b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            int textLength = destText.length() - 1;
789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
792d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
798d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
799d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
800d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
801d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
80222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
80322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
80422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
8056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
806d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
807d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
80877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
8090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
8110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
812d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
813d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
814d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
8150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
8176ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
818454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
819454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
8201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
8211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
8221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
8236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
8246ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
8256ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
8266ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
8276ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
8281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
8291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
8306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
8316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
8336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
837a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
838a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
845a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
846a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
847a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
848a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
849490556a764a879cd0eaff358e90705cc1335c92eErik                }
850d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
8516ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
852454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
853a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
854a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
8551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
8561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
8576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
8586ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
8596ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
8606ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
8611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
8621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
8631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
8641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
8650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
8670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
869c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
871c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
872c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
873c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
874c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
8838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
8848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
886c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
887c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
888c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
889c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
8938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
89595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
89695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
89795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
89895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
89995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
90095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
90195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
9068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
91095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
912c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
916d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
917c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
919e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
921e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
922e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
923e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
925c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
92695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
927e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
929e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
934e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
935e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
936e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
937e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
940c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
941c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
947e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
948e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
949e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
950e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
951045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
952045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
953045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
954045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
955045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
957045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
9588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
9594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
960045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
9614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
962dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
966e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
9704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
9824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
983054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
9854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
98749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        ListAdapter adapter = getAdapter();
98849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
98949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                && end == getSelectionEnd()) {
990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
992e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
993e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
994e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
995e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
99649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (editable.length() > tokenEnd + 1) {
99749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
99849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
99949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd++;
100049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1001a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
1002b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
1003e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
1004e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
10051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1006d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
1007d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1008d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
100971fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
10105ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
10115ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
1012d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
101349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
101449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // just committed.
101549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // For paste, it may not be as there are possibly multiple
101649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // tokens being added.
101749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (end == getSelectionEnd()) {
101849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    dismissDropDown();
101949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1020f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
10214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
1022d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
1023d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
10244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
1025d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
1026d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
10274031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10284031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
1029f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
10304031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
1034f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1037f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
1038f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
1039f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
1040f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1041c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
10424031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1043c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
1044c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
1045c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
1046c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
1047f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
1048f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
1049f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1050a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking != end) {
1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
1052f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1053f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
1054f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
1055f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
1056e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
10571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
10581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
10591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
10601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
10621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
10631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
106405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
10651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
1066e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1067e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1068e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
106939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
107039f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
107139f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
107239f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
107339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
1074e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
1075e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
1076e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1077e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
1078e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
10793b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
10803b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
10813b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
10823b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
1083a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (chipText != null) {
1084a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.replace(start, getSelectionEnd(), chipText);
1085a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            }
10863b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
1087054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
108805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
108905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
10908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
10918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
10928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
10938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1097b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1098b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1099b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1100b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
11082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
11104031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
11114031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1112ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1117b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1119b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1120b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1121b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1122b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
113249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (enoughToFilter() && !isCompletedToken(text)) {
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
114649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
114749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
114849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (TextUtils.isEmpty(text)) {
114949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return false;
115049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Check to see if this is a completed token before filtering.
115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int end = text.length();
115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String token = text.toString().substring(start, end).trim();
115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (!TextUtils.isEmpty(token)) {
115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
115849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
115949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return false;
116049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
116149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
116736d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
11728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
11738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
11748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
11758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
11768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
11778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1180d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1181d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1182d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1183d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1185d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1186c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
11876caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
11900436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1195c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
12018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
12028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1205b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1206c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1207c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1208c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1209416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
12105753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
12115753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
12125753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1217c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1221416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1222416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1223c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1224416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1225416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1226416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1230c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1234e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
123577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1236416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1238368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1239e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1240e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1244b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1251b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1252b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1253b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1254e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1255e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1256e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1257e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1258e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1259e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1260b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1261b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1262b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1263b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1264b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
12651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
12661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
12671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
12681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
12691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1270a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1272b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1275b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1276e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1277b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1278b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1292c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1293c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1294c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12964fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
12974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
12984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
13014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1302c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1306c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
13104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
13114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
13124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
13134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
13144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
13154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1317c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1321b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1322b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1323b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1326c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13304031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
133100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to add to the list of addresses.
133200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createAddressText(RecipientEntry entry) {
13338659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
13348659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
13358659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
13368659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
13378659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1338a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1339a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1340a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1341c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1342c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1343c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1344c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1345a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
13468659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
134700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String trimmedDisplayText = token.toString().trim();
13488659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
13498659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
135000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
135100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
135200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
135300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
135400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to display in a chip.
135500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
135600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String display = entry.getDisplayName();
135700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String address = entry.getDestination();
135800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
135900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            display = null;
136000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
136100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (address != null) {
136200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Tokenize out the address in case the address already
136300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // contained the username as well.
136400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
136500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
136600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                address = tokenized[0].getAddress();
136700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
136800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
136900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (!TextUtils.isEmpty(display)) {
137000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return display;
137100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
137200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return address;
137300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else {
137400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return new Rfc822Token(display, address, null).toString();
137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
13768659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
13778659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1378fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
137900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String displayText = createAddressText(entry);
1380a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
1381a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            return null;
1382a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        }
1383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1384b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int textLength = displayText.length()-1;
1385c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1386c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
13896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
13906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
13926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
14028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
14038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
14048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1406c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1409c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
14110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
14120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
14131e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
14141e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
14151e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1419c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
14234221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
14244221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
14254221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
14264221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1427f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
14310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
14320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
14330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
14350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
14360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
14370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
14391e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (RecipientEntry.isCreatedRecipient(item.getContactId())
14401e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                && (TextUtils.isEmpty(item.getDisplayName())
14411e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || TextUtils.equals(item.getDisplayName(), destination)
14421e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || (mValidator != null && !mValidator.isValid(destination)))) {
14430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
14440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
14450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
14460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
14480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
14490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
145300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
14547a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
14557a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
14567a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
14577a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
146200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
146300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
146400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */ Collection<Long> getDataIds() {
146500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        final Set<Long> result = new HashSet<Long>();
146600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip [] chips = getSortedRecipients();
146700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null) {
146800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            for (RecipientChip chip : chips) {
146900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                result.add(chip.getDataId());
147000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
147100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
147200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return result;
147383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
147483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
14754031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
147600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */RecipientChip[] getSortedRecipients() {
147700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] recips = getSpannable()
147800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .getSpans(0, getText().length(), RecipientChip.class);
14796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
148000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .asList(recips));
14816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
14826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
14836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
14846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
14856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
14866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
14876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
14886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
14896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
14916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
14926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
14946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
14956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
14966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
14976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
14986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
14996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
15004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
15024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
15074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
15114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
15158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
15168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
15174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
15194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1522a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1523a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ImageSpan getMoreChip() {
152422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
152522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
152622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
152722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
152822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
15298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1530045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1531045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1532045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
15338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1534a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1535a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void createMoreChip() {
1536bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1537bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1538bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1539bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
15406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
15416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
15426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
15436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
15446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
154583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
15460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
15496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
155083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
15514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1552c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1553c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1554c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1555c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1556c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1557c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
15584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
15594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
15604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
156122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        int adjustedHeight = height;
156222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        Layout layout = getLayout();
156322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (layout != null) {
156422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            adjustedHeight -= layout.getLineDescent(0);
156522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
156622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
15674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
15694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
15706f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
15714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1572368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
15734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
15740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
15750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1578368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
15794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
15804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
15816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1582368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1583368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
15849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1585368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
15869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1587368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1588368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
15899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
15906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
15916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
15926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
15936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
159477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1595368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
15964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
159777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
159877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
159977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
16004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
160177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
16020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
16034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
16058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
16068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
16078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
16088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1609a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1610a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /*package*/ void removeMoreChip() {
16114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
16124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
16134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
16144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
16154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
16164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
16174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1618c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
161964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
162064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1621c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1622c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1623c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
162464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
16254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
16264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
16276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
16280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
16290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
16306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
16316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
163264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
163364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
163464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
163564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
163664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
163764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1638bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1639bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1640bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1641bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1642bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
16434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
16444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
16454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
16464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1649c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1653b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1655b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1656b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1657b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1658b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1659b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1660c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
166100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip selectChip(RecipientChip currentChip) {
16621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
16651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
16661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
16671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
16692f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1671b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1672b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1673b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1675b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1676b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1680b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1681fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1682b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
168383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1684b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
168583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
16868b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
168783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1688b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
16891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
16901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1691c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
16921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
16936ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1694b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1695b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
16961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
16981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
16991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
17001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
17011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
17021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1706b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
17111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
17151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
17161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
17186ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1720fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1722fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1723c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
17241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
17251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
17261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
17271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
17281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
17291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1730e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
17311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
17351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
17391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
17401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
17421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
17431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
17441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
17451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
17461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
17471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1752b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
175300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private void unselectChip(RecipientChip chip) {
1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1757c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
17595753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            Log.w(TAG,
17605753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    "The chip doesn't exist or may be a chip a user was editing");
17615753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
17625753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1764c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1765b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17668b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
17678b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
17688b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
17698b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17708b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
17718b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17728b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1773c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1774b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1775b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1780c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1785b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1786b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
17953656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1796b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1797b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1798b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1799ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
1800ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1802b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1804b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1805b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int toDelete = spanEnd;
1806b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1807b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1808b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1809b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1811b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        // Always remove trailing spaces when removing a chip.
1812b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1813b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            toDelete++;
1814b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        }
1815b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1816b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        text.delete(spanStart, toDelete);
1817b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1819c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1822b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1823b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1824b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
182600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
182700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) {
1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1829b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1830b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1832b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1833b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1834b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1835b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1836b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1837a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (chipText != null) {
1838a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (start == -1 || end == -1) {
1839a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1840a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.insert(0, chipText);
1841a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            } else {
1842a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
1843a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // There may be a space to replace with this chip's new
1844a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // associated
1845a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // space. Check for it
1846a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    int toReplace = end;
1847a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
1848a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
1849a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                        toReplace++;
1850a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    }
1851a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    editable.replace(start, toReplace, chipText);
1852b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                }
1853b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1855b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1857b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1858c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1860c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1863b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1865b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1866b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1867b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1868b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1870b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1871b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1872c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1873c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1874b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1875c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1876368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1877368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1878368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1879368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1880311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1881311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1882311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1883311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1884311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1885311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
18891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
18901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
18911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
18921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
18931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
18941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
18951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
18961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
18971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
18981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
18991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
19001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
19011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
19021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
19031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
19041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
19051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1906368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
19095753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
19105753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
19115753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
1912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1918054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1920054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
192176ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1922054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1923054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1924054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1925054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1926054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1927054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1928e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1929e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1930e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1931e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1932e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1936e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
19373b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1939e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1940e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1941e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1942e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1943e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
19461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1949e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1950e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
195122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
1952e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1953e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
195477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
19553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
19563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
19573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
19583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void handlePasteClip(ClipData clip) {
19593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeTextChangedListener(mTextWatcher);
19603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
19613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
19623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            for (int i = 0; i < clip.getItemCount(); i++) {
19633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                CharSequence paste = clip.getItemAt(i).getText();
19643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                if (paste != null) {
19653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int start = getSelectionStart();
19663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int end = getSelectionEnd();
19673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    Editable editable = getText();
19683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    if (start >= 0 && end >= 0 && start != end) {
19693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.append(paste, start, end);
19703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    } else {
19713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.insert(end, paste);
19723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    }
19733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    handlePasteAndReplace();
19743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                }
19753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
19763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
19773e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
19783e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mHandler.post(mAddTextWatcher);
19793e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
19803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
198149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    @Override
198249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    public boolean onTextContextMenuItem(int id) {
198349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (id == android.R.id.paste) {
198449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
198549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    Context.CLIPBOARD_SERVICE);
19863e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            handlePasteClip(clipboard.getPrimaryClip());
198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return true;
198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return super.onTextContextMenuItem(id);
199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
199149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
19921e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    private void handlePasteAndReplace() {
19931e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = handlePaste();
19941e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (created != null && created.size() > 0) {
19951e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            // Perform reverse lookups on the pasted contacts.
19961e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            IndividualReplacementTask replace = new IndividualReplacementTask();
19971e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            replace.execute(created);
19981e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        }
19991e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    }
20001e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira
200149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
20021e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    /* package */ArrayList<RecipientChip> handlePaste() {
200349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String text = getText().toString();
200449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
200549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int tokenStart = originalTokenStart;
200749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int prevTokenStart = tokenStart;
200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        RecipientChip findChip = null;
20091e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = new ArrayList<RecipientChip>();
201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenStart != 0) {
201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            // There are things before this!
201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            while (tokenStart != 0 && findChip == null) {
201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                prevTokenStart = tokenStart;
201449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                findChip = findChip(tokenStart);
201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (tokenStart != originalTokenStart) {
201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (findChip != null) {
201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = prevTokenStart;
202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                int tokenEnd;
202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                RecipientChip createdChip;
202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                while (tokenStart < originalTokenStart) {
202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
202649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    createdChip = findChip(tokenStart);
202749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    // +1 for the space at the end.
202849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
20291e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                    created.add(createdChip);
203049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
203149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
203249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
203349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Take a look at the last token. If the token has been completed with a
203449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // commit character, create a chip.
203549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (isCompletedToken(lastAddress)) {
203649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            Editable editable = getText();
20371e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
20381e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            commitChip(tokenStart, editable.length(), editable);
20391e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            created.add(findChip(tokenStart));
204049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
20411e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        return created;
204249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
204349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
204449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
204549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
204649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd >= length()) {
204749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return tokenEnd;
204849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
204949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
205049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
205149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
205249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
205349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // This token had not only an end token character, but also a space
205449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // separating it from the next token.
205549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
205649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
205749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
205849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return tokenEnd;
205949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
206049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
206177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
206277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
206377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
206477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
206577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
207177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
207477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
207577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
20806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
208577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
208677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
208777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
208877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
208900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                addresses[i] = createAddressText(originalRecipients.get(i).getEntry());
209077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
209177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
209477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
209577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
20961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
209777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
209877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
20991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
21001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
210177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
210477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
210577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
210677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
211577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
2120b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
21212e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                                oldText.removeSpan(chip);
2122b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
21236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
21246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
2125b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
21266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
212777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
212877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
212977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
213077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
21312e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                        setText(text);
213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
213377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
213477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
213677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
213877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
213977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
214077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
214877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
214977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
215000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                addresses[i] = createAddressText(originalRecipients.get(i).getEntry());
215177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
215277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
215377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
215477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
21551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
215677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
215777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
21581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
21591e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase()));
216077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
216177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
216277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
216377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
216477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
216577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
216677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
216777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
216877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
216977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
217077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
217177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
217277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
2173b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
21746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
21756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
21766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
21776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
21786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
21796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
21806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
21816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
21826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
21836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
21846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
2185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2186b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
2187b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
22063e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            if (mDragEnabled) {
22073e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Start drag-and-drop for the selected chip.
22083e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                startDrag(currentChip);
22093e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            } else {
22103e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Copy the selected chip email address.
22113e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                showCopyDialog(currentChip.getEntry().getDestination());
22123e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
22133e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22143e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22153e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22163e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22173e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Enables drag-and-drop for chips.
22183e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22193e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public void enableDrag() {
22203e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mDragEnabled = true;
22213e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22223e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22233e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22243e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Starts drag-and-drop for the selected chip.
22253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void startDrag(RecipientChip currentChip) {
22273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        String address = currentChip.getEntry().getDestination();
22283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
22293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Start drag mode.
22313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
22323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Remove the current chip, so drag-and-drop will result in a move.
22343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // TODO (phamm): consider readd this chip if it's dropped outside a target.
22353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeChip(currentChip);
22363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles drag event.
22403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    @Override
22423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public boolean onDragEvent(DragEvent event) {
22433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        switch (event.getAction()) {
22443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_STARTED:
22453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Only handle plain text drag and drop.
22463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
22473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_ENTERED:
22483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                requestFocus();
22493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
22503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DROP:
22513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                handlePasteClip(event.getClipData());
22523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
22533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        return false;
22553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Drag shadow for a {@link RecipientChip}.
22593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private final class RecipientChipShadow extends DragShadowBuilder {
22613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        private final RecipientChip mChip;
22623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public RecipientChipShadow(RecipientChip chip) {
22643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip = chip;
22653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
22683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
22693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            Rect rect = mChip.getDrawable().getBounds();
22703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowSize.set(rect.width(), rect.height());
22713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowTouchPoint.set(rect.centerX(), rect.centerY());
22723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
22753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onDrawShadow(Canvas canvas) {
22763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip.getDrawable().draw(canvas);
2277b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2278b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2279b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2280b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
2281b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
2282b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
2283b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
2284b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
2285b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
2286b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
2287b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
2288b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
2289b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2290b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2291b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2292b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2293b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2294b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2295b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2296b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2297b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2298b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
2299b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2300b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2301b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2302b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2303b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2304b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2305b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2306b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2307b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2308b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2309b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2310b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2311b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2312b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2313b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2314b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2315b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2316b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2317b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2318b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2319b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2320b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
232177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2322