RecipientEditTextView.java revision 6fe9af5b1cc380243f0fad9354ab7bd41ac2983b
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;
412e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        float[] widths = new float[1];
413e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        paint.getTextWidths(" ", widths);
41400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
415e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira                calculateAvailableWidth(true) - deleteWidth - widths[0]);
4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
4241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
42977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
43097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
43197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
43297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
434f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
435f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
436f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
437f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
438f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
439f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
447045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
448e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
452e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
454e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        float[] widths = new float[1];
455e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        paint.getTextWidths(" ", widths);
45600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
457e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira                calculateAvailableWidth(false) - iconWidth - widths[0]);
4581a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4591a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
463c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
467045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
468045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
469045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
470045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4749024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
47690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
47990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
48090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
48190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
48290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
48390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
492ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                if (photo != null) {
493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Rect backgroundPadding = new Rect();
495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    mChipBackground.getPadding(backgroundPadding);
496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            0 + backgroundPadding.top,
498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            width - backgroundPadding.right,
499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            height - backgroundPadding.bottom);
500ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Matrix matrix = new Matrix();
501ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
502ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                }
504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
5059024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
5069024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
508e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
50997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
510379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
51197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
5131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
5151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
51800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /**
51900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     * Get the background drawable for a RecipientChip.
52000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     */
52100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
52200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ Drawable getChipBackground(RecipientEntry contact) {
52300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
52400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                mChipBackground : mInvalidChipBackground;
52500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
52600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
52797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
52897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
5293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        paint.getTextBounds(text, 0, text.length(), bounds);
53097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
53197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
53297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
53397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
53400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
5361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
5371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
541c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
54477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
548e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
551045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
55777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
56077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
565045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
566045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5675519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5685519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
570c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5715519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
572416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
57397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
57497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
575f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
576f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5874f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5884f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5894f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5914f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5941426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5961426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
597c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
600c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
60143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
602045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
603d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
610b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
611b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
612b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
613b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
614b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
615b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
616b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
617b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
618a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
619a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
620a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        mMoreItem = moreItem;
621a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    }
622a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
626ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipBackground = chipBackground;
627ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
628ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
629ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
630ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipHeight(int height) {
631ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipHeight = height;
632ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
633ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
637bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
638bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
639bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
640bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
641bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
642bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
643bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
644bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
645c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
646c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
647c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
64853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (width != 0 && height != 0) {
64953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            if (mPendingChipsCount > 0) {
65053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                postHandlePendingChips();
65153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            } else {
65253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                checkChipWidths();
65353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
6547bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
65577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
65600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
658416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
659416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
660416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
661416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
662416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
663416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
66400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            mTriedGettingScrollView = true;
665416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
666c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
667c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
668a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
669a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
670a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
671a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
672a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void checkChipWidths() {
67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        // Check the widths of the associated chips.
67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (chips != null) {
67753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            Rect bounds;
67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            for (RecipientChip chip : chips) {
67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                bounds = chip.getDrawable().getBounds();
68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    // Need to redraw that chip.
68253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    replaceChip(chip, chip.getEntry());
68353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                }
68453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
685a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
68653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    }
68753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void handlePendingChips() {
6897bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
6907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
6917bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
6927bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
6937bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
6947bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
69553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
69653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (mPendingChipsCount <= 0) {
69753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            return;
69853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        }
69953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
710a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
711a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
714a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
7150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
7170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
71800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            sanitizeEnd();
71922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
721a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
722a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
725a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
726a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
727a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
728a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
72977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
730a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
732a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
733a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
73422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
735a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
73677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
73777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
738a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
739a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
7450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
74600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
74700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void sanitizeEnd() {
7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
74900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
75322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
75700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                lastSpan = getLastChip();
7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
7750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
7760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
7781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
7791e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
7801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
7811e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
7821e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
783b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
7841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
7850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
7860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
7870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
79000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            String destText = createAddressText(entry);
791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
792b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            int textLength = destText.length() - 1;
793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
798d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
799d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
800d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
801d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
803d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
804d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
805d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
80622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
80722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
80822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
8096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
810d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
811d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
81277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
8130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
8150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
816d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
817d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
818d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
8190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
8201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
8216ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
822454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
823454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
8241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
8251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
8261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
8276ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
8286ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
8296ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
8306ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
8316ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
8321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
8331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
8346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
8356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
8376ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8386ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
845a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
846a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
847a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
848a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
849a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
850a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
851a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
852a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
853490556a764a879cd0eaff358e90705cc1335c92eErik                }
854d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
856454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
857a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
858a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
8591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
8601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
8616ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
8626ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
8636ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
8646ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
8651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
8661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
8671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
8681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
8690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
8710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
873c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
874c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
875c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
876c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
877c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
878c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
8838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
8848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
891c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
892c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
893c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
8978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
898c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
89995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
90095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
90195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
90295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
90395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
90495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
90595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
9068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
9108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
9118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
9128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
9138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
91495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
916c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
917c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
919c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
920d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
922c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
926e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
927e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
93095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
934e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
935e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
936e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
937e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
939e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
942c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
944c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
945c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
947e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
948e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
949e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
950e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
951e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
952e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
953e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
954e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
955045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
957045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
958045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
959045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
960045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
961045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
9628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
9634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
964045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
9654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
966dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
9744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
983e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
985e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
9864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
987054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
9894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
99149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        ListAdapter adapter = getAdapter();
99249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
99349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                && end == getSelectionEnd()) {
994e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
995e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
996e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
997e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
998e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
100049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (editable.length() > tokenEnd + 1) {
100149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
100249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
100349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd++;
100449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1005a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
1006b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
1007e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
1008e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
10091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1010d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
1011d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1012d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
101371fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
10145ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
10155ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
1016d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
101749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
101849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // just committed.
101949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // For paste, it may not be as there are possibly multiple
102049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // tokens being added.
102149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (end == getSelectionEnd()) {
102249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    dismissDropDown();
102349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1024f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
10254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
1026d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
1027d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
10284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
1029d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
1030d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
10314031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10324031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
10344031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
1037f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
1038f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
1039f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
1040f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1041f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
1042f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
1043f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
1044f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1045c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
10464031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1047c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
1048c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
1049c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
1050c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
1052f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
1053f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1054a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking != end) {
1055f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
1056f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1057f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
1058f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
1059f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
1060e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
10621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
10631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
10641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
10651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
10661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
10671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
106805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
10691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
1070e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1071e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1072e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
107339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
107439f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
107539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
107639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
107739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
1078e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
1079e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
1080e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1081e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
1082e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
10833b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
10843b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
10853b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
10863b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
10876fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira            int selEnd = getSelectionEnd();
10886fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira            if (chipText != null && start > -1 && selEnd > -1) {
10896fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira                editable.replace(start, selEnd, chipText);
1090a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            }
10913b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
1092054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
109305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
109405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
10958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
10968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
10978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
10988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1101b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1102b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1103b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1104b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1105b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
11132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
11154031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
11164031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1117ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1120b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1121b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1122b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1126b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1127b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
113749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (enoughToFilter() && !isCompletedToken(text)) {
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (TextUtils.isEmpty(text)) {
115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return false;
115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Check to see if this is a completed token before filtering.
115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int end = text.length();
115849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
115949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String token = text.toString().substring(start, end).trim();
116049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (!TextUtils.isEmpty(token)) {
116149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
116249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
116349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
116449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return false;
116549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
116649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
117236d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
11778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
11788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
11798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
11808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
11818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
11828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1185d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1186d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1187d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1188d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1190d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
11926caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
11950436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1199c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1201c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
12068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
12078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1214416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
12155753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
12165753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
12175753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1224c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1225c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1226416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1227416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1228c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1229416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1230416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1231416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1234b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1235c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1236b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1239e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
124077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1241416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1243368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1244e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1245e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1251b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1252b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1253b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1254b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1255b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1258b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1259e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1260e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1261e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1262e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1263e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1264e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1265b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1266b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
12701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
12711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
12721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
12731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
12741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1275a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1276b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1277b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1278b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1279b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1280b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1281e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1282b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1283b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1292c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1293c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1294c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1297c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1298c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
13024fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
13034fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
13064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1307c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1311c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
13154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
13164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
13174fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
13184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
13194fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
13204fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1322c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1326b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1327b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1328b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1330c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13354031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
133600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to add to the list of addresses.
133700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createAddressText(RecipientEntry entry) {
13388659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
13398659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
13408659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
13418659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
13428659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1343a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1344a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1345a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1346c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1347c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1348c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1349c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1350a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
13518659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
135200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String trimmedDisplayText = token.toString().trim();
13538659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
13548659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
135500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
135600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
135700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
135800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
135900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to display in a chip.
136000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
136100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String display = entry.getDisplayName();
136200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String address = entry.getDestination();
136300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
136400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            display = null;
136500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
136600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (address != null) {
136700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Tokenize out the address in case the address already
136800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // contained the username as well.
136900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
137000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
137100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                address = tokenized[0].getAddress();
137200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
137300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
137400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (!TextUtils.isEmpty(display)) {
137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return display;
137600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
137700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return address;
137800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else {
137900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return new Rfc822Token(display, address, null).toString();
138000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
13818659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
13828659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1383fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
138400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String displayText = createAddressText(entry);
1385a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
1386a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            return null;
1387a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        }
1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1389b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int textLength = displayText.length()-1;
1390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
13946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
13956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
13976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1402c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
14078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
14088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
14098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
14160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
14170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
14181e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
14191e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
14201e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1423c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1426c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
14284221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
14294221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
14304221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
14314221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1432f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
14360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
14370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
14400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
14410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
14420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
14430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
14441e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (RecipientEntry.isCreatedRecipient(item.getContactId())
14451e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                && (TextUtils.isEmpty(item.getDisplayName())
14461e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || TextUtils.equals(item.getDisplayName(), destination)
14471e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || (mValidator != null && !mValidator.isValid(destination)))) {
14480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
14490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
14500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
14510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
14530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
14540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
145800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
14597a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
14607a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
14617a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
14627a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1463c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
146700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
146800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
146900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */ Collection<Long> getDataIds() {
147000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        final Set<Long> result = new HashSet<Long>();
147100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip [] chips = getSortedRecipients();
147200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null) {
147300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            for (RecipientChip chip : chips) {
147400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                result.add(chip.getDataId());
147500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
147600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
147700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return result;
147883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
147983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
14804031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
148100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */RecipientChip[] getSortedRecipients() {
148200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] recips = getSpannable()
148300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .getSpans(0, getText().length(), RecipientChip.class);
14846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
148500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .asList(recips));
14866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
14876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
14886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
14896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
14916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
14926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
14946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
14956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
14966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
14976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
14986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
14996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
15006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
15016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
15026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
15036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
15046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
15054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
15074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
15164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
15208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
15218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
15224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
15244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1527a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1528a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ImageSpan getMoreChip() {
152922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
153022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
153122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
153222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
153322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
15348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1535045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1536045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1537045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
15388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1539a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1540a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void createMoreChip() {
1541bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1542bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1543bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1544bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
15456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
15466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
15476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
15486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
15496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
155083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
15510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
15546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
155583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
15564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1557c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1558c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1559c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1560c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1561c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1562c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
15634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
15644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
15654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
156622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        int adjustedHeight = height;
156722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        Layout layout = getLayout();
156822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (layout != null) {
156922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            adjustedHeight -= layout.getLineDescent(0);
157022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
157122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
15724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
15744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
15756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
15764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1577368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
15784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
15790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
15800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1583368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
15844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
15854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
15866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1587368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1588368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
15899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1590368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
15919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1592368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1593368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
15949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
15956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
15966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
15976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
15986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
159977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1600368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
16014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
160277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
160377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
160477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
16054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
160677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
16070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
16084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
16108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
16118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
16128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
16138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1615a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /*package*/ void removeMoreChip() {
16164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
16174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
16184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
16194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
16204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
16214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
16224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1623c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
162464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
162564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1626c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1627c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1628c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
162964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
16304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
16314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
16326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
16330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
16340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
16356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
16366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
163764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
163864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
163964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
164064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
164164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
164264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1643bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1644bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1645bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1646bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1647bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
16484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
16494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
16504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
16514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1654c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1655b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1656b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1657b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1658b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1659b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1660b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1661b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1662b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1663b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1664b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1665c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
166600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip selectChip(RecipientChip currentChip) {
16671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
16691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
16711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
16721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
16731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
16742f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1676b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1680b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1681b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1682b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1684b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1685b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1686fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1687b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
168883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1689b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
169083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
16918b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
169283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1693b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
16941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
16951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1696c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
16986ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1699b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1700b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
17011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
17021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
17061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1711b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
17151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
17161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
17201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
17211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
17236ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
17241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1725fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1727fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1728c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
17291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
17301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
17311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1735e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
17391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
17401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
17411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
17421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
17431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
17441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
17451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
17471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
17481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
17491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
17501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
17511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
17521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1757b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
175800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private void unselectChip(RecipientChip chip) {
1759b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1762c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
17645753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            Log.w(TAG,
17655753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    "The chip doesn't exist or may be a chip a user was editing");
17665753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
17675753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1768b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1769c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17718b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
17728b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
17738b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
17748b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17758b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
17768b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17778b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1783c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1785c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1786b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1795b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1796b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1797b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1798b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1799b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
18003656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1802b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1804ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
1805ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
1806b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1807b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1808b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1809b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1810b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int toDelete = spanEnd;
1811b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1814b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1816b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        // Always remove trailing spaces when removing a chip.
1817b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1818b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            toDelete++;
1819b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        }
1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1821b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        text.delete(spanStart, toDelete);
1822b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1823b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1824c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1827b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1829b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1830b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
183100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
183200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) {
1833b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1834b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1835b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1837b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1838b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1839b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1840b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1841b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1842a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (chipText != null) {
1843a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (start == -1 || end == -1) {
1844a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1845a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.insert(0, chipText);
1846a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            } else {
1847a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
1848a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // There may be a space to replace with this chip's new
1849a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // associated
1850a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // space. Check for it
1851a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    int toReplace = end;
1852a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
1853a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
1854a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                        toReplace++;
1855a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    }
1856a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    editable.replace(start, toReplace, chipText);
1857b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                }
1858b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1859c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1860b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1863c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1866b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1867b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1868b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1870b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1871b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1872b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1873b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1874b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1875b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1876b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1877c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1878c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1879b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1880c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1881368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1882368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1883368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1884368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1885311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1886311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1887311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1888311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1889311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1890311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
18941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
18951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
18961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
18971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
18981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
18991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
19001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
19011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
19021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
19031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
19041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
19051e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
19061e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
19071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
19081e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
19091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
19101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1911368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
19145753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
19155753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
19165753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
1917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1921e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1922e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1923054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1924e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1925054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
192676ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1927054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1928054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1929054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1930054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1931054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1932054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1936e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1937e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1939e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1940e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1941e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
19423b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
1943e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1946e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1949e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1950e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
19511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1952e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1953e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1954e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1955e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
195622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
1957e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1958e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
195977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
19603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
19613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
19623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
19633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void handlePasteClip(ClipData clip) {
19643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeTextChangedListener(mTextWatcher);
19653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
19663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
19673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            for (int i = 0; i < clip.getItemCount(); i++) {
19683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                CharSequence paste = clip.getItemAt(i).getText();
19693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                if (paste != null) {
19703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int start = getSelectionStart();
19713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int end = getSelectionEnd();
19723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    Editable editable = getText();
19733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    if (start >= 0 && end >= 0 && start != end) {
19743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.append(paste, start, end);
19753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    } else {
19763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.insert(end, paste);
19773e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    }
19783e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    handlePasteAndReplace();
19793e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                }
19803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
19813e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
19823e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
19833e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mHandler.post(mAddTextWatcher);
19843e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
19853e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
198649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    @Override
198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    public boolean onTextContextMenuItem(int id) {
198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (id == android.R.id.paste) {
198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    Context.CLIPBOARD_SERVICE);
19913e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            handlePasteClip(clipboard.getPrimaryClip());
199249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return true;
199349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
199449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return super.onTextContextMenuItem(id);
199549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
199649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
19971e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    private void handlePasteAndReplace() {
19981e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = handlePaste();
19991e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (created != null && created.size() > 0) {
20001e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            // Perform reverse lookups on the pasted contacts.
20011e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            IndividualReplacementTask replace = new IndividualReplacementTask();
20021e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            replace.execute(created);
20031e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        }
20041e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    }
20051e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira
200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
20071e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    /* package */ArrayList<RecipientChip> handlePaste() {
200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String text = getText().toString();
200949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int tokenStart = originalTokenStart;
201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int prevTokenStart = tokenStart;
201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        RecipientChip findChip = null;
20141e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = new ArrayList<RecipientChip>();
201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenStart != 0) {
201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            // There are things before this!
201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            while (tokenStart != 0 && findChip == null) {
201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                prevTokenStart = tokenStart;
201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                findChip = findChip(tokenStart);
202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (tokenStart != originalTokenStart) {
202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (findChip != null) {
202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = prevTokenStart;
202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
202649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                int tokenEnd;
202749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                RecipientChip createdChip;
202849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                while (tokenStart < originalTokenStart) {
202949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
203049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
203149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    createdChip = findChip(tokenStart);
203249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    // +1 for the space at the end.
203349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
20341e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                    created.add(createdChip);
203549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
203649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
203749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
203849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Take a look at the last token. If the token has been completed with a
203949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // commit character, create a chip.
204049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (isCompletedToken(lastAddress)) {
204149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            Editable editable = getText();
20421e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
20431e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            commitChip(tokenStart, editable.length(), editable);
20441e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            created.add(findChip(tokenStart));
204549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
20461e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        return created;
204749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
204849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
204949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
205049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
205149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd >= length()) {
205249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return tokenEnd;
205349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
205449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
205549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
205649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
205749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
205849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // This token had not only an end token character, but also a space
205949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // separating it from the next token.
206049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
206149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
206249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
206349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return tokenEnd;
206449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
206549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
207177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
207477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
207577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
208077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
20856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
208677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
208777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
208877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
208977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
209077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
209177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
209400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                addresses[i] = createAddressText(originalRecipients.get(i).getEntry());
209577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
209677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
209777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
209877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
209977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
210077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
21011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
21041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
21051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
210677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
211577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
212077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
212177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
212277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
212377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
212477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
2125b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
21262e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                                oldText.removeSpan(chip);
2127b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
21286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
21296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
2130b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
21316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
213377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
213477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
21362e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                        setText(text);
213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
213877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
213977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
214077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
214877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
214977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
215077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
215177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
215277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
215377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
215477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
215500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                addresses[i] = createAddressText(originalRecipients.get(i).getEntry());
215677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
215777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
215877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
215977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
21601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
216177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
216277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
21631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
21641e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase()));
216577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
216677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
216777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
216877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
216977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
217077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
217177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
217277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
217377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
217477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
217577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
217677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
217777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
2178b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
21796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
21806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
21816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
21826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
21836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
21846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
21856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
21866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
21876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
21886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
21896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2206b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
2207b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
2208b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
2209b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
2210b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
22113e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            if (mDragEnabled) {
22123e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Start drag-and-drop for the selected chip.
22133e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                startDrag(currentChip);
22143e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            } else {
22153e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Copy the selected chip email address.
22163e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                showCopyDialog(currentChip.getEntry().getDestination());
22173e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
22183e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22193e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22203e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22213e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22223e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Enables drag-and-drop for chips.
22233e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22243e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public void enableDrag() {
22253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mDragEnabled = true;
22263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Starts drag-and-drop for the selected chip.
22303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void startDrag(RecipientChip currentChip) {
22323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        String address = currentChip.getEntry().getDestination();
22333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
22343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Start drag mode.
22363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
22373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Remove the current chip, so drag-and-drop will result in a move.
22393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // TODO (phamm): consider readd this chip if it's dropped outside a target.
22403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeChip(currentChip);
22413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles drag event.
22453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    @Override
22473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public boolean onDragEvent(DragEvent event) {
22483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        switch (event.getAction()) {
22493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_STARTED:
22503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Only handle plain text drag and drop.
22513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
22523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_ENTERED:
22533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                requestFocus();
22543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
22553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DROP:
22563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                handlePasteClip(event.getClipData());
22573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
22583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        return false;
22603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
22613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
22633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Drag shadow for a {@link RecipientChip}.
22643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
22653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private final class RecipientChipShadow extends DragShadowBuilder {
22663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        private final RecipientChip mChip;
22673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public RecipientChipShadow(RecipientChip chip) {
22693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip = chip;
22703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
22733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
22743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            Rect rect = mChip.getDrawable().getBounds();
22753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowSize.set(rect.width(), rect.height());
22763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowTouchPoint.set(rect.centerX(), rect.centerY());
22773e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22783e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
22793e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
22803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onDrawShadow(Canvas canvas) {
22813e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip.getDrawable().draw(canvas);
2282b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2283b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2284b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2285b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
2286b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
2287b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
2288b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
2289b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
2290b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
2291b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
2292b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
2293b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
2294b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2295b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2296b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2297b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2298b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2299b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2300b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2301b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2302b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2303b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
2304b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2305b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2306b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2307b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2308b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2309b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2310b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2311b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2312b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2313b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2314b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2315b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2316b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2317b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2318b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2319b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2320b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2321b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2322b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2323b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2324b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2325b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
232677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2327