RecipientEditTextView.java revision a11c2b0e1ea043aa4d8745285f2dcbe20448b417
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;
26a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereiraimport android.content.res.Resources;
27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap;
281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory;
29c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix;
313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.graphics.Point;
32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
34c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
3677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask;
37156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler;
38156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message;
3922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable;
402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
41572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
49c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
50c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
57a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.view.DragEvent;
59b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
61c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
66572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
67416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
69c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
70b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
71c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
72156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
74e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow;
75416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
76c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
77b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
78a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira
79a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
81b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
8477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
86c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
87c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
912d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
92b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
93b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
94e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
95e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        PopupWindow.OnDismissListener {
96c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
9800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
9900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
10000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
10100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
10200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
10600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
10700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final long DISMISS_DELAY = 300;
10800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
1094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
1108faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
1118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ static final int CHIP_LIMIT = 2;
1128faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
1138faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    private static final int MAX_CHIPS_PARSED = 50;
1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
11500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int sSelectedTextColor = -1;
11600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
11700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Resources for displaying chips.
118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mInvalidChipBackground;
12300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mChipBackgroundPressed;
12500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipHeight;
12700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipFontSize;
12900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Validator mValidator;
135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
144c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
146054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
147a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
148156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
149156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
150c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
151c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
1528faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    private boolean mNoChips = false;
1538faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
154b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
155b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
15877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
15977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
162bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
163bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
166b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
167b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
168b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
169b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
170b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
171b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
172b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
173b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
174a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
175a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
176b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
177b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
178b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
179416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
18000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Obtain the enclosing scroll view, if it exists, so that the view can be
18400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // scrolled to show the last line of chips content.
185416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
186416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
18700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private boolean mTriedGettingScrollView;
188416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1893e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private boolean mDragEnabled = false;
1903e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
191e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
192e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
193e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
194e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
195e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
196e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
197e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
198e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
199e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
200e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
20177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
20277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
203a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
204a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
205a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
206a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
207a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
208a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
209a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
210a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
211a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
212a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    private Runnable mDelayedShrink = new Runnable() {
213a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
214a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        @Override
215a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        public void run() {
216a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            shrink();
217a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        }
218a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
219a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    };
220a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
2212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
223a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        setChipDimensions();
22477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
22577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
22677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
228e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
230e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2310436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
234b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
235b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
236368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
237e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
241368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
244b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
247c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
249156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
250156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
251156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
252156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
253b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
254156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
255156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
256156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
257156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
258156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2596ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2606ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
261b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
262ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
263cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
26400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ RecipientChip getLastChip() {
26500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = null;
26600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
26700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null && chips.length > 0) {
26800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            last = chips[chips.length - 1];
269ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
27000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return last;
2714fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2724fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2734fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2744fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2754fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2764fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
27700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = getLastChip();
2788faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (last != null && start < getSpannable().getSpanEnd(last)) {
27900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Grab the last chip and set the cursor to after it.
28000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
281d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
282d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
283d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
284d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
28522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    @Override
28622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    public void onRestoreInstanceState(Parcelable state) {
28722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (!TextUtils.isEmpty(getText())) {
28822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(null);
28922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        } else {
29022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(state);
29122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
29222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
29322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
29400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    @Override
2957c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    public Parcelable onSaveInstanceState() {
2967c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
2977c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        clearSelectedChip();
2987c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        return super.onSaveInstanceState();
2997c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    }
3007c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira
301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
303c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
304c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
307c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
308c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
311001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
313c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
314c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
3154f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
31605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
317c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
318c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
319c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
320a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
321c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
322c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
323a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
324a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
325a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
326a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
3276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
328c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
329c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
330d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
331d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
332b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
333d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
3354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
3364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
337416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
3384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3402d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
341b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
342b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
343b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
344b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
345b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
34707a230a45345b16793463e32194f820eafa2a626Mindy Pereira        if (mTokenizer == null) {
34807a230a45345b16793463e32194f820eafa2a626Mindy Pereira            return;
34907a230a45345b16793463e32194f820eafa2a626Mindy Pereira        }
3505a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3515a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
354a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            if (getWidth() <= 0) {
355a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
356a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // and there is no reason to attempt to commit chips yet.
357a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // This focus lost must be the result of an orientation change
358a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // or an initial rendering.
359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // Re-post the shrink for later.
360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
361a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.post(mDelayedShrink);
362a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                return;
363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
364001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
366001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
367a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
368001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
371001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
372001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
373001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
374a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    Editable text = getText();
375a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
376a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    // This token was already tokenized, so skip past the ending token.
3778a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
378a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                        whatEnd++;
379a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    }
380001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
381001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
382a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int selEnd = getSelectionEnd();
3834ef6086da2f4bcac5a6cba773f828734a399a879Mindy Pereira                    if (whatEnd != selEnd) {
384001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
385001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
386001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
387001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
388001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
389090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
390001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
40077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
40177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
40277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
40377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
40477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
40577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
4064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
4074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
409e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
410c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
411c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
412c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
413c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
414c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
417e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
421e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
423e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        float[] widths = new float[1];
424e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        paint.getTextWidths(" ", widths);
42500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
426e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira                calculateAvailableWidth(true) - deleteWidth - widths[0]);
4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
4301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
4351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
44077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
44197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
44297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
44397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
445f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
446f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
447f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
448f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
449f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
450f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
458045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
459e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
462c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
463e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
465e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        float[] widths = new float[1];
466e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        paint.getTextWidths(" ", widths);
46700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
468e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira                calculateAvailableWidth(false) - iconWidth - widths[0]);
4691a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4701a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
476c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
477c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
478045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
479045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
480045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
481045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
48690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
48790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
48890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
48990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
49090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
49190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
49290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
49390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
49490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4959024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4969024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4979024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4989024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4999024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
5009024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
5019024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
5029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                if (photo != null) {
504ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
505ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Rect backgroundPadding = new Rect();
506ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    mChipBackground.getPadding(backgroundPadding);
507ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
508ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            0 + backgroundPadding.top,
509ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            width - backgroundPadding.right,
510ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            height - backgroundPadding.bottom);
511ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Matrix matrix = new Matrix();
512ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
513ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
514ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                }
515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
5169024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
5179024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
519e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
52097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
521379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
52297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
523c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
5241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
5261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
528c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
52900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /**
53000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     * Get the background drawable for a RecipientChip.
53100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     */
53200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
53300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ Drawable getChipBackground(RecipientEntry contact) {
53400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
53500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                mChipBackground : mInvalidChipBackground;
53600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
53700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
53897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
53997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
5403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        paint.getTextBounds(text, 0, text.length(), bounds);
54197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
54297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
54397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
54497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
54500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
5471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
5491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
552c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
55577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
559e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
560c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
562045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
565c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
56877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
57177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5732d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5742d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
576045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
577045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5785519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5795519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
581c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5825519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
583416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
58497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
58597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
586f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
587f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
597a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira
598a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira    private void setChipDimensions() {
599a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        Resources r = getContext().getResources();
600a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mChipBackground = r.getDrawable(R.drawable.chip_background);
601a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mChipBackgroundPressed = r.getDrawable(R.drawable.chip_background_selected);
602a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mChipDelete = r.getDrawable(R.drawable.chip_delete);
603a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
604a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mAlternatesLayout = R.layout.chips_alternate_item;
605a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mDefaultContactPhoto =  BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
606a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.more_item, null);
607a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mChipHeight = r.getDimension(R.dimen.chip_height);
608a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mChipFontSize = r.getDimension(R.dimen.chip_text_size);
609a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
610a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira        mCopyViewRes = R.layout.copy_chip_dialog_layout;
611a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira    }
612a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira
613c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
6144f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
6154f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
6164f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
6171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
6184f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
6191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
6201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
6211426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
6221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
6231426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
624c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
625b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
626b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
627a11c2b0e1ea043aa4d8745285f2dcbe20448b417Mindy Pereira     * @deprecated
628c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
62943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
630045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
631d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
632b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
633b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
634b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
635b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
636b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
637b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
638b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
639b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
640b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
641b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
642b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
643b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
644b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
645b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
646a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
647a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
648a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        mMoreItem = moreItem;
649a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    }
650a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
651ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
652ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
653ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
654ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipBackground = chipBackground;
655ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
656ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
657ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
658ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipHeight(int height) {
659ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipHeight = height;
660ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
661ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
662bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
663bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
664bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
665bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
666bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
667bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
668bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
669bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
670bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
671bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
672bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
673c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
674c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
675c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (width != 0 && height != 0) {
67753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            if (mPendingChipsCount > 0) {
67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                postHandlePendingChips();
67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            } else {
68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                checkChipWidths();
68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
6827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
68377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
68400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
685416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
686416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
687416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
688416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
689416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
690416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
691416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
69200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            mTriedGettingScrollView = true;
693416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
694c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
695c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
70153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void checkChipWidths() {
70253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        // Check the widths of the associated chips.
70353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
70453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (chips != null) {
70553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            Rect bounds;
70653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            for (RecipientChip chip : chips) {
70753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                bounds = chip.getDrawable().getBounds();
70853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
70953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    // Need to redraw that chip.
71053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    replaceChip(chip, chip.getEntry());
71153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                }
71253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
71453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    }
71553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
7168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
7178faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ void handlePendingChips() {
7188faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (getViewWidth() <= 0) {
7197bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
7207bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
7217bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
7227bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
7237bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
72453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (mPendingChipsCount <= 0) {
72553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            return;
72653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        }
72753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
728a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
729a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
730a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
7318faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
7328faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                for (int i = 0; i < mPendingChips.size(); i++) {
7338faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    String current = mPendingChips.get(i);
7348faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    int tokenStart = editable.toString().indexOf(current);
7358faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    int tokenEnd = tokenStart + current.length();
7368faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    if (tokenStart >= 0) {
7378faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        // When we have a valid token, include it with the token
7388faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        // to the left.
7398faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        if (tokenEnd < editable.length() - 2
7408faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                                && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
7418faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                            tokenEnd++;
7428faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        }
7438faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        createReplacementChip(tokenStart, tokenEnd, editable);
744a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
7458faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    mPendingChipsCount--;
7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7478faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                sanitizeEnd();
7488faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            } else {
7498faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                mNoChips = true;
7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7518faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
75222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
753a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
754a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
755a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
756a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
757a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
758a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
759a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
760a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
761a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
76277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
763a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
764a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
765a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
766a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
76722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
768a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
76977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
77077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
771a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
772a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
7730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7768faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
7778faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ int getViewWidth() {
7788faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return getWidth();
7798faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
7808faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
7810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
7830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
78400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
78500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void sanitizeEnd() {
7860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
78700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
7880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
7890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
7900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
79122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
7920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
7930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
7940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
79500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                lastSpan = getLastChip();
7960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
7990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
8000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
8010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
8020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
8030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
8040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
8050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
8060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
8070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
8080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
8110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
8120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
8130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
8140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
8150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
8161e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
8171e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
8181e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
8191e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
8201e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
821b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
8221e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
8230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
8240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
8250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
827d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
82800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            String destText = createAddressText(entry);
829d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
830b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            int textLength = destText.length() - 1;
831d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
832d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
833d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
834d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
835d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
8368faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (!mNoChips) {
8378faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    chip = constructChipSpan(entry, start, false);
8388faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8398faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
840d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
841d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
842d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
843d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
844d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
845d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
84622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
84722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
84822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
8496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
850d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
851d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
85277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
8530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
8550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
856d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
857d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
858d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
8590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
8601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
8616ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
862454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
863454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
8641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
8651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
8661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
8676ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
8686ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
8696ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
8706ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
8716ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
8721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
8731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
8746ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
8756ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
876a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
8776ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8786ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
879a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
880a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
881a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
882a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
883a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
884a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
885a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
886a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
887a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
888a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
889a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
890a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
891a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
892a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
893490556a764a879cd0eaff358e90705cc1335c92eErik                }
894d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
8956ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
896454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
897a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
898a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
8991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
9001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
9016ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
9026ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
9036ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
9046ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
9051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
9061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
9071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
9081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
9090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
9101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
9110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
9120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
913c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
916c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
917c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
9208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
9218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
9228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
9238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
9248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
9258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
9278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
9288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
9298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
930c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
931c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
932c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
933c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
9378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
93995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
94095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
94195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
94295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
94395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
94495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
94595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
9468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
9488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
9498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
9508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
9518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
9528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
9538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
95495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
955c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
960d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
963e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
964e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
965e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
966e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
967e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
968e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
97095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
971e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
972e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
973e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
974e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
975e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
976e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
977e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
978e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
979e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
980e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
981e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
982c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
983c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
984c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
985c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
986e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
987e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
988e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
989e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
990e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
991e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
992e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
993e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
994e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
995045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
996045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
997045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
998045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
999045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
1000045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
1001045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
10028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
10034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
1004045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
10054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
1006dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
1007e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
1008e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
1009e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
1010e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
1011e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
1012e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
1013e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
10144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
1015e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
1016e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1017e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
1018e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1019e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1020e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
1021e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1022e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
1023e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
1024e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
1025e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
10264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1027054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
1028e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
10294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1030e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
103149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        ListAdapter adapter = getAdapter();
103249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
103349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                && end == getSelectionEnd()) {
1034e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
1035e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
1036e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
1037e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
1038e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
1039e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
104049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (editable.length() > tokenEnd + 1) {
104149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
104249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
104349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd++;
104449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1045a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
1046b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
1047e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
1048e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
10491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1050d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
1051d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1052d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
105371fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
10545ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
10555ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
1056d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
105749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
105849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // just committed.
105949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // For paste, it may not be as there are possibly multiple
106049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // tokens being added.
106149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (end == getSelectionEnd()) {
106249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    dismissDropDown();
106349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1064f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
10654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
1066d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
1067d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
10684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
1069d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
1070d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
10714031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10724031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
1073f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
10744031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
1075f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
1076f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
1077f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
1078f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
1079f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
1080f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1081f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
1082f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
1083f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
1084f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1085c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
10864031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1087c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
1088c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
1089c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
1090c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
1091f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
1092f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
1093f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1094ec35d9975b61c84cda7081ebfce9e017229fb3d5Mindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking < end) {
1095f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
1096f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1097f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
1098f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
1099f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
1100e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
11018faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
11021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
11031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
11041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
11058faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (mNoChips) {
11068faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            return true;
11078faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
11081e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
11091e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
11101e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
111105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
11121e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
1113e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1114e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1115e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
111639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
111739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
111839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
111939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
112039f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
1121e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
1122e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
1123e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1124e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
1125e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
11263b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
11273b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
11283b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
11293b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
11306fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira            int selEnd = getSelectionEnd();
11316fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira            if (chipText != null && start > -1 && selEnd > -1) {
11326fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira                editable.replace(start, selEnd, chipText);
1133a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            }
11343b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
1135054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
113605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
113705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
11388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
11408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
11418684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1147b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1148b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
11562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11572d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
11584031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
11594031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1160ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1170b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
118049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (enoughToFilter() && !isCompletedToken(text)) {
1181c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1185c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1186c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
119449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
119549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
119649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (TextUtils.isEmpty(text)) {
119749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return false;
119849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
119949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Check to see if this is a completed token before filtering.
120049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int end = text.length();
120149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
120249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String token = text.toString().substring(start, end).trim();
120349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (!TextUtils.isEmpty(token)) {
120449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
120549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
120649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
120749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return false;
120849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
120949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
121536d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1217c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
12208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
12218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
12228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
12238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
12248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
12258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1226c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1228d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1229d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1230d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1231d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1232c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1233d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1234c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
12356caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1236b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1237b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
12380436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1243c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1244c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1245c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1246c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
12498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
12508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1251b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1253b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1254c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1255c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1256c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1257416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
12585753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
12595753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
12605753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1261c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1264c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1265c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1266c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1267c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1268c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1269416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1270416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1271c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1272416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1273416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1274416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1275b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1276b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1277b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1278c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1279b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1280b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1281b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1282e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
128377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1284416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1285b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1286368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1287e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1288e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1289b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1290b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1291b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1292b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1293b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1294b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1295b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1296b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1297b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1298b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1299b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1300b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1301b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1302e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1303e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1304e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1305e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1306e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1307e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1308b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1309b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1310b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1311b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1312b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
13131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
13141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
13151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
13161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
13171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1318a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1319b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1320b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1321b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1322b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1323b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1324e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1325b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1326b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1330c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1335c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1336c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1337c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1338c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1339c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1340c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1341c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1342c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1343c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
13454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
13464fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1347c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
13494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1350c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1355c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1356c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
13584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
13594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
13604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
13614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
13624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
13634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1364c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1365c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1369b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1370b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1371b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1372c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1373c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1374c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1375c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1376c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13784031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
137900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to add to the list of addresses.
138000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createAddressText(RecipientEntry entry) {
13818659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
13828659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
13838659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
13848659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
13858659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1386a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1387a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1388a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1389c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1390c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1391c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1392c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1393a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
13948659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
139500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String trimmedDisplayText = token.toString().trim();
13968659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
13978659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
139800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
139900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
140000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
140100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
140200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to display in a chip.
140300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
140400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String display = entry.getDisplayName();
140500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String address = entry.getDestination();
140600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
140700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            display = null;
140800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
140900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (address != null) {
141000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Tokenize out the address in case the address already
141100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // contained the username as well.
141200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
141300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
141400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                address = tokenized[0].getAddress();
141500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
141600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
141700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (!TextUtils.isEmpty(display)) {
141800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return display;
141900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
142000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return address;
142100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else {
142200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return new Rfc822Token(display, address, null).toString();
142300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
14248659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
14258659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1426fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
142700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String displayText = createAddressText(entry);
1428a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
1429a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            return null;
1430a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        }
14318faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        SpannableString chipText = null;
1432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
14358faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int textLength = displayText.length()-1;
14368faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        chipText = new SpannableString(displayText);
14378faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (!mNoChips) {
14388faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            try {
14398faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                RecipientChip chip = constructChipSpan(entry, start, pressed);
14408faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                chipText.setSpan(chip, 0, textLength,
14418faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14428faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                chip.setOriginalText(chipText.toString());
14438faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            } catch (NullPointerException e) {
14448faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14458faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                return null;
14468faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            }
1447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
14528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
14538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
14548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
14610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
14620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
14631e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
14641e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
14651e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1472c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
14734221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
1474920c95167574395b1ff9ecc16e41e13853269bbcMindy Pereira        if (chip != null && start >= 0 && end >= 0) {
14754221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
14764221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1477f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
14810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
14820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
14830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
14850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
14860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
14870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
14880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
14891e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (RecipientEntry.isCreatedRecipient(item.getContactId())
14901e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                && (TextUtils.isEmpty(item.getDisplayName())
14911e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || TextUtils.equals(item.getDisplayName(), destination)
14921e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || (mValidator != null && !mValidator.isValid(destination)))) {
14930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
14940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
14950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
14960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
14980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
14990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
150300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
15047a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
15057a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
15067a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
15077a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
151200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
151300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
151400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */ Collection<Long> getDataIds() {
151500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        final Set<Long> result = new HashSet<Long>();
151600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip [] chips = getSortedRecipients();
151700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null) {
151800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            for (RecipientChip chip : chips) {
151900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                result.add(chip.getDataId());
152000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
152100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
152200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return result;
152383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
152483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
15254031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
152600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */RecipientChip[] getSortedRecipients() {
152700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] recips = getSpannable()
152800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .getSpans(0, getText().length(), RecipientChip.class);
15296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
153000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .asList(recips));
15316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
15326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
15336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
15346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
15356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
15366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
15376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
15386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
15396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
15406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
15416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
15426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
15436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
15446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
15456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
15466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
15476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
15486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
15496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
15504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
15524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
15574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
15614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
15658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
15668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
15674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
15694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1572a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1573a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ImageSpan getMoreChip() {
157422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
157522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
157622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
157722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
157822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
15798faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    private MoreImageSpan createMoreSpan(int count) {
15808faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), count);
15818faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
15828faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
15838faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
15848faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
15858faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                + mMoreItem.getPaddingRight();
15868faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int height = getLineHeight();
15878faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
15888faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Canvas canvas = new Canvas(drawable);
15898faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int adjustedHeight = height;
15908faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Layout layout = getLayout();
15918faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (layout != null) {
15928faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            adjustedHeight -= layout.getLineDescent(0);
15938faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
15948faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
15958faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
15968faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
15978faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        result.setBounds(0, 0, width, height);
15988faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return new MoreImageSpan(result);
15998faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
16008faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
16018faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
16028faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ void createMoreChipPlainText() {
16038faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        // Take the first <= CHIP_LIMIT addresses and get to the end of the second one.
16048faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Editable text = getText();
16058faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int start = 0;
16068faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int end = start;
16078faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        for (int i = 0; i < CHIP_LIMIT; i++) {
16088faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            end = movePastTerminators(mTokenizer.findTokenEnd(text, start));
16098faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            start = end; // move to the next token and get its end.
16108faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
16118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        // Now, count total addresses.
16128faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        start = 0;
16138faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int tokenCount = countTokens(text);
16148faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT);
16158faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(end, text.length()));
16168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
16178faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        text.replace(end, text.length(), chipText);
16188faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        mMoreChip = moreSpan;
16198faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
16208faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
16218faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
16228faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /* package */int countTokens(Editable text) {
16238faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int tokenCount = 0;
16248faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int start = 0;
16258faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        while (start < text.length()) {
16268faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            start = movePastTerminators(mTokenizer.findTokenEnd(text, start));
16278faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            tokenCount++;
16288faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            if (start >= text.length()) {
16298faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                break;
16308faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            }
16318faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
16328faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return tokenCount;
16338faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
16348faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
16358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1636045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1637045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1638045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
16398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1640a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1641a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void createMoreChip() {
16428faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (mNoChips) {
16438faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            createMoreChipPlainText();
16448faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            return;
16458faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
16468faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
1647bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1648bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1649bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1650bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
16516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
16526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
16536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
16546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
16556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
16568faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
165783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
16580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
16590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
16604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
166283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
16634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
16648faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(overage);
1665368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
16664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
16674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
16686f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1669368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1670368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
16719024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1672368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
16739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1674368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1675368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
16769024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
16776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
16786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
16796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
16806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
168177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1682368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
16834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16848faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (totalReplaceEnd < text.length()) {
16858faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            totalReplaceEnd = text.length();
16868faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
168777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
168877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
168977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
16904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
169177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
16920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
16934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
16958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
16968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
16978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
16988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1699a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1700a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /*package*/ void removeMoreChip() {
17014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
17024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
17034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
17044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
17054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
17064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
17074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1708c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
170964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
171064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1711c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1712c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1713c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
171464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
17154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
17164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
17176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
17180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
17190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
17206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
17216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
172264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
172364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
172464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
172564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
172664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
172764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1728bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1729bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1730bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1731bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1732bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
17334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
17344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
17354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
17364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
17374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
17384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1739c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1740b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1741b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1742b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1743b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1744b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1745b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1746b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1750c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
175100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip selectChip(RecipientChip currentChip) {
17521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
17531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
17541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
17551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
17561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
17571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
17581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
17592f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
17601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1762b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1764b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1765b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
17668faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (mNoChips) {
17678faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    return null;
17688faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
1769b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1771b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1772b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1773b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1774fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1775b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
177683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
177883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
17798b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
178083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
17821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
17831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1784c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
17851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
17866ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
17891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
17901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
17911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
17921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
17931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
17941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
17951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
17961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
17981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1799b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
18001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
18011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
18021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
18031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
18041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
18051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
18061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
18071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
18081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
18091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
18101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
18116ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
18121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1813fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1814b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1815fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
18171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
18181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
18191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
18201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
18211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
18221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1823e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
18241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
18251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
18261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
18271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
18281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
18291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
18301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
18311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
18321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
18331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
18341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
18351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
18361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
18371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
18381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
18391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
18401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1841b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1842b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
18438faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira     * the chip without a delete icon and with an unfocused background. This is
18448faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira     * called when the RecipientChip no longer has focus.
1845b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
184600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private void unselectChip(RecipientChip chip) {
1847b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1848b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1849b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1850c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1851b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
18528faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
18535753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
18545753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1855b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1856c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1857b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
18588b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
18598b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
18608faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (!mNoChips) {
18618faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
18628faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
18638faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
18648b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
18658b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
18668b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1868b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1870b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1871b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1872c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1873b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1874c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1875b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1876b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1877b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1878b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1879b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1880b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1881b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1882b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1883b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1884b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1885b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1886b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1887b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1888b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
18893656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1890b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1891b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1892b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1893ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
1894ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
1895b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1896b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1897b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1898b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1899b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int toDelete = spanEnd;
1900b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1901b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1902b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1903b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1904c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1905b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        // Always remove trailing spaces when removing a chip.
1906b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1907b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            toDelete++;
1908b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        }
1909b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1910b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        text.delete(spanStart, toDelete);
1911b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1912b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1914b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1916b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1917b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1918b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1919b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
192000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
192100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) {
1922b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1923b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1924b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1925c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1926b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1927b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1928b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1929b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1930b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1931a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (chipText != null) {
1932a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (start == -1 || end == -1) {
1933a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1934a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.insert(0, chipText);
1935a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            } else {
1936a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
1937a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // There may be a space to replace with this chip's new
1938a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // associated
1939a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // space. Check for it
1940a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    int toReplace = end;
1941a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
1942a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
1943a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                        toReplace++;
1944a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    }
1945a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    editable.replace(start, toReplace, chipText);
1946b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                }
1947b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1949b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1950b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1951b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1952c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1953b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1954c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1955b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1956b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1957b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1958b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1959b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1960b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1961b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1962b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1963b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1964b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1965b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1968b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1970368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1971368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1972368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1973368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1974311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1975311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1976311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1977311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1978311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1979311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
19831e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
19841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
19851e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
19861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
19871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
19881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
19891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
19901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
19911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
19921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
19931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
19941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
19951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
19961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
19971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
19981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
19991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
2000368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
2001e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
2002e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
20035753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
20045753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
20055753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
2006e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
2007e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
2008e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
2009e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
2010e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
2011e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
2012054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
2013e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
2014054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
201576ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
2016054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
2017054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
2018054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
2019054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
2020054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
2021054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
2022e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
2023e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
2024e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
2025e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
2026e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
2027e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
2028e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
2029e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
2030e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
20313b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
2032e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
2033e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
2034e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
2035e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
2036e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
2037e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
2038e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
2039e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
20401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
2041e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
2042e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
2043e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
2044e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
204522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
2046e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
2047e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
204877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
20493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
20503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
20513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
20523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void handlePasteClip(ClipData clip) {
20533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeTextChangedListener(mTextWatcher);
20543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
20553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
20563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            for (int i = 0; i < clip.getItemCount(); i++) {
20573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                CharSequence paste = clip.getItemAt(i).getText();
20583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                if (paste != null) {
20593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int start = getSelectionStart();
20603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int end = getSelectionEnd();
20613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    Editable editable = getText();
20623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    if (start >= 0 && end >= 0 && start != end) {
20633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.append(paste, start, end);
20643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    } else {
20653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.insert(end, paste);
20663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    }
20673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    handlePasteAndReplace();
20683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                }
20693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
20703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
20713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
20723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mHandler.post(mAddTextWatcher);
20733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
20743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
207549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    @Override
207649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    public boolean onTextContextMenuItem(int id) {
207749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (id == android.R.id.paste) {
207849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
207949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    Context.CLIPBOARD_SERVICE);
20803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            handlePasteClip(clipboard.getPrimaryClip());
208149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return true;
208249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
208349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return super.onTextContextMenuItem(id);
208449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
208549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
20861e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    private void handlePasteAndReplace() {
20871e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = handlePaste();
20881e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (created != null && created.size() > 0) {
20891e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            // Perform reverse lookups on the pasted contacts.
20901e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            IndividualReplacementTask replace = new IndividualReplacementTask();
20911e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            replace.execute(created);
20921e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        }
20931e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    }
20941e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira
209549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
20961e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    /* package */ArrayList<RecipientChip> handlePaste() {
209749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String text = getText().toString();
209849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
209949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
210049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int tokenStart = originalTokenStart;
210149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int prevTokenStart = tokenStart;
210249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        RecipientChip findChip = null;
21031e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = new ArrayList<RecipientChip>();
210449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenStart != 0) {
210549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            // There are things before this!
210649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            while (tokenStart != 0 && findChip == null) {
210749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                prevTokenStart = tokenStart;
210849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
210949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                findChip = findChip(tokenStart);
211049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
211149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (tokenStart != originalTokenStart) {
211249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (findChip != null) {
211349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = prevTokenStart;
211449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
211549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                int tokenEnd;
211649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                RecipientChip createdChip;
211749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                while (tokenStart < originalTokenStart) {
211849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
211949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
212049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    createdChip = findChip(tokenStart);
212149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    // +1 for the space at the end.
212249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
21231e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                    created.add(createdChip);
212449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
212549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
212649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
212749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Take a look at the last token. If the token has been completed with a
212849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // commit character, create a chip.
212949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (isCompletedToken(lastAddress)) {
213049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            Editable editable = getText();
21311e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
21321e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            commitChip(tokenStart, editable.length(), editable);
21331e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            created.add(findChip(tokenStart));
213449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
21351e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        return created;
213649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
213749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
213849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
213949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
214049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd >= length()) {
214149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return tokenEnd;
214249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
214349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
214449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
214549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
214649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
214749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // This token had not only an end token character, but also a space
214849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // separating it from the next token.
214949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
215049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
215149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
215249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return tokenEnd;
215349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
215449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
215577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
215677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
215777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
21588faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (mNoChips) {
21598faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    return null;
21608faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
216177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
216277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
216377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
216477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
216577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
216677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
216777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
216877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
216977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
217077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
217177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
217277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
217377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
217477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
217577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
217677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
21776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
217877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
217977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
218077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
218177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
218277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
218377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
218477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
2185039c9266e72538fd40d382de56807408385fc3daMindy Pereira            RecipientChip chip;
218677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2187039c9266e72538fd40d382de56807408385fc3daMindy Pereira                chip = originalRecipients.get(i);
2188039c9266e72538fd40d382de56807408385fc3daMindy Pereira                if (chip != null) {
2189039c9266e72538fd40d382de56807408385fc3daMindy Pereira                    addresses[i] = createAddressText(chip.getEntry());
2190039c9266e72538fd40d382de56807408385fc3daMindy Pereira                }
219177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
219277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
219377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
219477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
219577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
219677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
21971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
219877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
219977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
22001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
22011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
220277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
220377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
220477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
220577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
220677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
220777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
220877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
220977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
221077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
221177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
221277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
221377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
221477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
221577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
221677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
221777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
221877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
221977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
222077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
2221b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
22222e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                                oldText.removeSpan(chip);
2223b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
22246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
22256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
2226b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
22276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
222877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
222977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
223077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
223177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
22322e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                        setText(text);
223377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
223477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
223577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
223677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
223777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
223877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
223977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
224077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
224177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
224277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
224377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
224477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
224577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
224677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
224777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
224877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
224977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
2250039c9266e72538fd40d382de56807408385fc3daMindy Pereira            RecipientChip chip;
225177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2252039c9266e72538fd40d382de56807408385fc3daMindy Pereira                chip = originalRecipients.get(i);
2253039c9266e72538fd40d382de56807408385fc3daMindy Pereira                if (chip != null) {
2254039c9266e72538fd40d382de56807408385fc3daMindy Pereira                    addresses[i] = createAddressText(chip.getEntry());
2255039c9266e72538fd40d382de56807408385fc3daMindy Pereira                }
225677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
225777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
225877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
225977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
22601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
226177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
226277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
22631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
22641e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase()));
226577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
226677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
226777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
226877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
226977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
227077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
227177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
227277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
227377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
227477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
227577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
227677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
227777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
2278b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
22796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
22806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
22816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
22826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
22836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
22846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
22856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
22866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
22876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
22886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
22896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
2290b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2291b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
2292b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2293b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2294b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2295b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2296b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2297b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2298b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2299b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2300b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2301b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2302b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
2303b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
2304b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
2305b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2306b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
2307b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
2308b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
2309b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
2310b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
23113e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            if (mDragEnabled) {
23123e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Start drag-and-drop for the selected chip.
23133e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                startDrag(currentChip);
23143e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            } else {
23153e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Copy the selected chip email address.
23163e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                showCopyDialog(currentChip.getEntry().getDestination());
23173e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
23183e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
23193e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23203e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23213e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23223e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Enables drag-and-drop for chips.
23233e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23243e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public void enableDrag() {
23253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mDragEnabled = true;
23263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Starts drag-and-drop for the selected chip.
23303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void startDrag(RecipientChip currentChip) {
23323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        String address = currentChip.getEntry().getDestination();
23333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
23343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Start drag mode.
23363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
23373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Remove the current chip, so drag-and-drop will result in a move.
23393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // TODO (phamm): consider readd this chip if it's dropped outside a target.
23403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeChip(currentChip);
23413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles drag event.
23453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    @Override
23473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public boolean onDragEvent(DragEvent event) {
23483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        switch (event.getAction()) {
23493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_STARTED:
23503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Only handle plain text drag and drop.
23513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
23523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_ENTERED:
23533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                requestFocus();
23543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
23553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DROP:
23563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                handlePasteClip(event.getClipData());
23573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
23583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
23593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        return false;
23603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Drag shadow for a {@link RecipientChip}.
23643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private final class RecipientChipShadow extends DragShadowBuilder {
23663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        private final RecipientChip mChip;
23673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public RecipientChipShadow(RecipientChip chip) {
23693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip = chip;
23703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
23713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
23733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
23743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            Rect rect = mChip.getDrawable().getBounds();
23753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowSize.set(rect.width(), rect.height());
23763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowTouchPoint.set(rect.centerX(), rect.centerY());
23773e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
23783e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23793e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
23803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onDrawShadow(Canvas canvas) {
23813e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip.getDrawable().draw(canvas);
2382b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2383b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2384b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2385b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
2386b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
2387b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
2388b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
2389b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
2390b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
2391b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
2392b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
2393b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
2394b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2395b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2396b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2397b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2398b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2399b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2400b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2401b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2402b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2403b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
2404b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2405b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2406b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2407b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2408b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2409b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2410b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2411b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2412b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2413b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2414b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2415b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2416b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2417b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2418b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2419b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2420b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2421b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2422b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2423b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2424b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2425b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
242677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2427