RecipientEditTextView.java revision 07a230a45345b16793463e32194f820eafa2a626
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/*
22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project
32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License.
62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at
72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software
112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and
142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License.
152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips;
182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
19b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.app.Dialog;
20b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipData;
2149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereiraimport android.content.ClipDescription;
22b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager;
232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context;
24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface;
25b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener;
26c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap;
271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory;
28c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix;
303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.graphics.Point;
31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
33c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
3577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask;
36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler;
37156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message;
3822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable;
392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
40572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
48c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
50c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
532d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
54c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
56a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.view.DragEvent;
58b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
60c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
65572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
66416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
69b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
70c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
71156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
73e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow;
74416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
75c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
76b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
77a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
79b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
8277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
90b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
91b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
92e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
93e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        PopupWindow.OnDismissListener {
94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
9600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
9700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
9800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
9900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
10000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
10400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
10500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static final long DISMISS_DELAY = 300;
10600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
1074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
1088faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
1098faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ static final int CHIP_LIMIT = 2;
1108faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
1118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    private static final int MAX_CHIPS_PARSED = 50;
1124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
11300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private static int sSelectedTextColor = -1;
11400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
11500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Resources for displaying chips.
116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mInvalidChipBackground;
12100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Drawable mChipBackgroundPressed;
12300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipHeight;
12500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
12600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private float mChipFontSize;
12700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private Validator mValidator;
133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
142c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
144054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
145a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
146156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
147156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
148c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
149c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
1508faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    private boolean mNoChips = false;
1518faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
152b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
153b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
15677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
15777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
160bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
161bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
166b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
167b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
168b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
169b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
170b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
171b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
172a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
173a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
174b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
175b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
176b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
177416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
17800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Obtain the enclosing scroll view, if it exists, so that the view can be
18200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // scrolled to show the last line of chips content.
183416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
184416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
18500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private boolean mTriedGettingScrollView;
186416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1873e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private boolean mDragEnabled = false;
1883e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
189e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
190e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
191e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
192e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
193e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
194e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
195e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
196e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
197e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
198e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
19977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
20077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
201a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
202a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
203a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
204a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
205a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
206a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
207a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
208a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
209a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
210a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    private Runnable mDelayedShrink = new Runnable() {
211a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
212a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        @Override
213a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        public void run() {
214a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            shrink();
215a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        }
216a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
217a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    };
218a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
2192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
22177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
22277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
22377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
225e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
227e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2280436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
233368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
234e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
235b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
236b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
238368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
244c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
246156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
247156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
248156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
249156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
251156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
252156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
253156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
254156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
255156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
258b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
259ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
260cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
26100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ RecipientChip getLastChip() {
26200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = null;
26300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
26400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null && chips.length > 0) {
26500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            last = chips[chips.length - 1];
266ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
26700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return last;
2684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2694fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2704fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2714fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2724fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2734fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
27400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip last = getLastChip();
2758faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (last != null && start < getSpannable().getSpanEnd(last)) {
27600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Grab the last chip and set the cursor to after it.
27700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
278d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
279d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
280d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
281d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
28222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    @Override
28322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    public void onRestoreInstanceState(Parcelable state) {
28422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (!TextUtils.isEmpty(getText())) {
28522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(null);
28622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        } else {
28722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(state);
28822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
28922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
29022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
29100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    @Override
2927c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    public Parcelable onSaveInstanceState() {
2937c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
2947c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        clearSelectedChip();
2957c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        return super.onSaveInstanceState();
2967c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    }
2977c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira
298c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
300c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
303c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
304c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
308001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
311c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
3124f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
31305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
314c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
315c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
316c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
317a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
318c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
319c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
320a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
321a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
322a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
323a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
3246ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
325c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
326c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
327d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
328d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
329b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
330d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
3314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
3324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
334416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
3354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
338b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
339b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
340b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
341b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
342b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
34407a230a45345b16793463e32194f820eafa2a626Mindy Pereira        if (mTokenizer == null) {
34507a230a45345b16793463e32194f820eafa2a626Mindy Pereira            return;
34607a230a45345b16793463e32194f820eafa2a626Mindy Pereira        }
3475a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3485a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
351a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            if (getWidth() <= 0) {
352a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
353a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // and there is no reason to attempt to commit chips yet.
354a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // This focus lost must be the result of an orientation change
355a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // or an initial rendering.
356a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // Re-post the shrink for later.
357a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
358a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.post(mDelayedShrink);
359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                return;
360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
361001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
362001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
363001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
364a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
366001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
367001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
368001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
371a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    Editable text = getText();
372a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
373a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    // This token was already tokenized, so skip past the ending token.
3748a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
375a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                        whatEnd++;
376a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    }
377001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
378001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
379a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int selEnd = getSelectionEnd();
3804ef6086da2f4bcac5a6cba773f828734a399a879Mindy Pereira                    if (whatEnd != selEnd) {
381001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
382001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
383001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
384001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
385001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
386090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
387001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
39777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
39877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
39977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
40077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
40177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
40277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
4034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
4044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
407c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
408c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
409c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
410c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
411c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
414e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
418e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
420e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        float[] widths = new float[1];
421e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        paint.getTextWidths(" ", widths);
42200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
423e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira                calculateAvailableWidth(true) - deleteWidth - widths[0]);
4241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
430c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
4321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
4351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
43777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
43897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
43997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
44097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
442f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
443f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
444f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
445f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
446f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
447f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
454c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
455045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
456e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
460e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
462e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        float[] widths = new float[1];
463e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira        paint.getTextWidths(" ", widths);
46400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
465e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira                calculateAvailableWidth(false) - iconWidth - widths[0]);
4661a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4671a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
472c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
473c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
475045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
476045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
477045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
478045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
48390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
48490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
48590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
48690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
48790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
48890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
48990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
49090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
49190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4959024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4969024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4979024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4989024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4999024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
500ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                if (photo != null) {
501ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
502ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Rect backgroundPadding = new Rect();
503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    mChipBackground.getPadding(backgroundPadding);
504ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
505ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            0 + backgroundPadding.top,
506ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            width - backgroundPadding.right,
507ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            height - backgroundPadding.bottom);
508ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Matrix matrix = new Matrix();
509ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
510ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
511ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                }
512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
5139024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
5149024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
515c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
516e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
51797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
518379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
51997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
520c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
5211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
5231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
5241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
52600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /**
52700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     * Get the background drawable for a RecipientChip.
52800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira     */
52900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
53000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ Drawable getChipBackground(RecipientEntry contact) {
53100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
53200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                mChipBackground : mInvalidChipBackground;
53300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
53400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
53597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
53697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
5373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        paint.getTextBounds(text, 0, text.length(), bounds);
53897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
53997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
54097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
54197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
54200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
5441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
5451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
5461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
549c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
55277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
556e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
559045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
563c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
56577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
56877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
569c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
573045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
574045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5755519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5765519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
578c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5795519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
580416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
58197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
58297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
583f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
584f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5922d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5932d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
594c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5954f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5964f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5974f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5994f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
6001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
6011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
6021426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
6031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
6041426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
605c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
608c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
60943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
610045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
611d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
612b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
613b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
614b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
615b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
616b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
617b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
618b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
619b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
620b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
621b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
622b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
623b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
624b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
625b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
626a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
627a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
628a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        mMoreItem = moreItem;
629a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    }
630a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
631ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
632ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
633ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
634ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipBackground = chipBackground;
635ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
636ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
637ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
638ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipHeight(int height) {
639ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipHeight = height;
640ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
641ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
642bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
643bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
644bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
645bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
646bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
647bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
648bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
649bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
650bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
651bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
652bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
653c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
654c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
655c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
65653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (width != 0 && height != 0) {
65753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            if (mPendingChipsCount > 0) {
65853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                postHandlePendingChips();
65953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            } else {
66053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                checkChipWidths();
66153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
6627bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
66377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
66400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
665416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
666416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
667416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
668416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
669416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
670416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
671416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
67200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            mTriedGettingScrollView = true;
673416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
674c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
675c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
676a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
677a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
678a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
679a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
680a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void checkChipWidths() {
68253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        // Check the widths of the associated chips.
68353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
68453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (chips != null) {
68553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            Rect bounds;
68653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            for (RecipientChip chip : chips) {
68753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                bounds = chip.getDrawable().getBounds();
68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
68953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    // Need to redraw that chip.
69053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    replaceChip(chip, chip.getEntry());
69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                }
69253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
69453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    }
69553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
6968faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
6978faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ void handlePendingChips() {
6988faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (getViewWidth() <= 0) {
6997bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
7007bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
7017bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
7027bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
7037bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
70453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (mPendingChipsCount <= 0) {
70553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            return;
70653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        }
70753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
710a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
7118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
7128faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                for (int i = 0; i < mPendingChips.size(); i++) {
7138faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    String current = mPendingChips.get(i);
7148faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    int tokenStart = editable.toString().indexOf(current);
7158faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    int tokenEnd = tokenStart + current.length();
7168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    if (tokenStart >= 0) {
7178faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        // When we have a valid token, include it with the token
7188faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        // to the left.
7198faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        if (tokenEnd < editable.length() - 2
7208faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                                && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
7218faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                            tokenEnd++;
7228faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        }
7238faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        createReplacementChip(tokenStart, tokenEnd, editable);
724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
7258faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    mPendingChipsCount--;
7260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7278faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                sanitizeEnd();
7288faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            } else {
7298faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                mNoChips = true;
7300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7318faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
73222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
733a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
734a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
735a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
736a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
737a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
738a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
739a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
740a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
741a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
74277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
743a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
744a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
745a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
746a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
74722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
748a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
74977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
75077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
751a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
752a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7568faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
7578faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ int getViewWidth() {
7588faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return getWidth();
7598faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
7608faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
76400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
76500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void sanitizeEnd() {
7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
76700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
7700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
77122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
7720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
7730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
7740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
77500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                lastSpan = getLastChip();
7760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
7800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
7810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
7820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
7830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
7840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
7850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
7870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
7930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
7940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
7961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
7971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
7981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
7991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
8001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
801b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
8021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
8030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
8040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
8050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
807d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
80800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            String destText = createAddressText(entry);
809d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
810b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            int textLength = destText.length() - 1;
811d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
812d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
813d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
814d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
815d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
8168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (!mNoChips) {
8178faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    chip = constructChipSpan(entry, start, false);
8188faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
8198faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
820d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
821d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
822d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
823d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
824d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
825d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
82622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
82722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
82822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
8296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
830d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
831d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
83277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
8330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
8350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
836d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
837d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
838d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
8390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
8401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
8416ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
842454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
843454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
8441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
8451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
8461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
8476ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
8486ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
8496ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
8506ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
8516ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
8521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
8531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
8546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
856a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
8576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8586ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
859a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
860a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
861a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
862a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
863a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
864a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
865a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
866a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
867a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
868a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
869a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
870a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
871a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
872a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
873490556a764a879cd0eaff358e90705cc1335c92eErik                }
874d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
8756ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
876454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
877a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
878a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
8791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
8801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
8816ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
8826ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
8836ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
8846ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
8851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
8861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
8871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
8881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
8890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
8910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
893c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
895c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
896c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
897c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
898c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
912c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
9178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
91995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
92095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
92195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
92295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
92395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
92495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
92595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
9268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
9288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
9298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
9308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
9318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
9328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
9338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
93495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
940d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
941c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
942c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
946e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
947e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
948e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
95095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
951e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
952e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
953e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
954e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
955e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
956e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
957e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
958e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
959e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
960e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
961e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
966e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
967e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
968e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
969e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
970e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
971e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
972e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
973e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
974e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
975045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
976045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
977045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
978045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
979045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
980045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
981045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
9828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
9834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
984045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
9854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
986dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
989e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
992e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
993e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
9944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
995e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
996e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
997e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
998e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1000e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
1001e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1002e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
1003e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
1004e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
1005e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
10064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1007054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
1008e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
10094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1010e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
101149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        ListAdapter adapter = getAdapter();
101249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
101349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                && end == getSelectionEnd()) {
1014e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
1015e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
1016e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
1017e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
1018e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
1019e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
102049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (editable.length() > tokenEnd + 1) {
102149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
102249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
102349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd++;
102449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1025a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
1026b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
1027e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
1028e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
10291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1030d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
1031d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1032d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
103371fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
10345ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
10355ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
1036d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
103749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
103849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // just committed.
103949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // For paste, it may not be as there are possibly multiple
104049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // tokens being added.
104149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (end == getSelectionEnd()) {
104249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    dismissDropDown();
104349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1044f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
10454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
1046d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
1047d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
10484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
1049d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
1050d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
10514031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10524031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
1053f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
10544031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
1055f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
1056f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
1057f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
1058f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
1059f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
1060f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1061f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
1062f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
1063f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
1064f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1065c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
10664031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1067c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
1068c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
1069c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
1070c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
1071f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
1072f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
1073f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1074ec35d9975b61c84cda7081ebfce9e017229fb3d5Mindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking < end) {
1075f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
1076f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1077f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
1078f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
1079f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
1080e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
10818faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
10821e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
10831e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
10841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
10858faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (mNoChips) {
10868faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            return true;
10878faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
10881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
10891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
10901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
109105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
10921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
1093e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1094e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1095e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
109639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
109739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
109839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
109939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
110039f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
1101e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
1102e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
1103e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1104e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
1105e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
11063b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
11073b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
11083b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
11093b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
11106fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira            int selEnd = getSelectionEnd();
11116fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira            if (chipText != null && start > -1 && selEnd > -1) {
11126fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira                editable.replace(start, selEnd, chipText);
1113a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            }
11143b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
1115054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
111605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
111705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
11188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
11208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
11218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1126b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1127b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1128b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
11362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
11384031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
11394031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1140ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1147b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1148b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1149b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1150b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
116049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (enoughToFilter() && !isCompletedToken(text)) {
1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
117449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
117549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
117649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (TextUtils.isEmpty(text)) {
117749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return false;
117849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
117949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Check to see if this is a completed token before filtering.
118049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int end = text.length();
118149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
118249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String token = text.toString().substring(start, end).trim();
118349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (!TextUtils.isEmpty(token)) {
118449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
118549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
118649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
118749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return false;
118849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
118949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1192b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
119536d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
12008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
12018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
12028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
12038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
12048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
12058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1206c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1207c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1208d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1209d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1210d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1211d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1213d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
12156caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1216b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1217b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
12180436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1224c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1225c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1226c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1228c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
12298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
12308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1232c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1234c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1235c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1236c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1237416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
12385753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
12395753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
12405753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1243c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1244c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1245c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1246c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1249416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1250416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1251c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1252416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1253416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1254416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1255b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1258c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1259b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1260b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1261b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1262e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
126377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1264416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1265b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1266368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1267e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1268e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1270b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1272b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1275b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1276b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1277b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1278b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1279b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1280b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1281b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1282e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1283e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1284e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1285e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1286e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1287e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1288b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1289b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1290b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1291b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1292b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
12931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
12941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
12951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
12961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
12971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1298a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1299b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1300b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1301b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1302b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1303b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1304e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1305b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1306b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1311c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1317c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13244fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
13254fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
13264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
13294fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1330c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1335c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1336c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
13384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
13394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
13404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
13414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
13424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
13434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1344c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1345c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1346c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1347c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1349b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1350b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1351b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1355c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1356c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1357c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13584031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
135900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to add to the list of addresses.
136000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createAddressText(RecipientEntry entry) {
13618659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
13628659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
13638659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
13648659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
13658659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1366a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1367a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1368a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1369c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1370c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1371c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1372c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1373a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
13748659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String trimmedDisplayText = token.toString().trim();
13768659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
13778659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
137800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
137900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    }
138000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
138100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
138200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Use this method to generate text to display in a chip.
138300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
138400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String display = entry.getDisplayName();
138500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String address = entry.getDestination();
138600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
138700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            display = null;
138800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
138900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (address != null) {
139000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // Tokenize out the address in case the address already
139100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            // contained the username as well.
139200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
139300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
139400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                address = tokenized[0].getAddress();
139500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
139600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
139700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (!TextUtils.isEmpty(display)) {
139800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return display;
139900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
140000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return address;
140100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        } else {
140200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            return new Rfc822Token(display, address, null).toString();
140300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
14048659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
14058659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1406fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
140700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        String displayText = createAddressText(entry);
1408a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
1409a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            return null;
1410a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        }
14118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        SpannableString chipText = null;
1412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
14158faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int textLength = displayText.length()-1;
14168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        chipText = new SpannableString(displayText);
14178faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (!mNoChips) {
14188faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            try {
14198faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                RecipientChip chip = constructChipSpan(entry, start, pressed);
14208faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                chipText.setSpan(chip, 0, textLength,
14218faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14228faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                chip.setOriginalText(chipText.toString());
14238faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            } catch (NullPointerException e) {
14248faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14258faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                return null;
14268faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            }
1427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1430c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
14328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
14338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
14348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1435c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1436c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1437c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1438c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1439c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
14410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
14420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
14431e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
14441e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
14451e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
14534221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
1454920c95167574395b1ff9ecc16e41e13853269bbcMindy Pereira        if (chip != null && start >= 0 && end >= 0) {
14554221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
14564221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1457f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
14610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
14620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
14630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
14650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
14660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
14670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
14680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
14691e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (RecipientEntry.isCreatedRecipient(item.getContactId())
14701e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                && (TextUtils.isEmpty(item.getDisplayName())
14711e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || TextUtils.equals(item.getDisplayName(), destination)
14721e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                        || (mValidator != null && !mValidator.isValid(destination)))) {
14730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
14740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
14750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
14760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
14780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
14790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1480c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1481c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1482c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
148300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
14847a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
14857a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
14867a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
14877a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1489c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1490c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1491c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
149200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira
149300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
149400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */ Collection<Long> getDataIds() {
149500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        final Set<Long> result = new HashSet<Long>();
149600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip [] chips = getSortedRecipients();
149700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        if (chips != null) {
149800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            for (RecipientChip chip : chips) {
149900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                result.add(chip.getDataId());
150000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira            }
150100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        }
150200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        return result;
150383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
150483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
15054031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
150600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /* package */RecipientChip[] getSortedRecipients() {
150700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira        RecipientChip[] recips = getSpannable()
150800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .getSpans(0, getText().length(), RecipientChip.class);
15096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
151000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira                .asList(recips));
15116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
15126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
15136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
15146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
15156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
15166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
15176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
15186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
15196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
15206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
15216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
15226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
15236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
15246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
15256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
15266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
15276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
15286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
15296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
15304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
15324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
15374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
15414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
15458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
15468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
15474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
15484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
15494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
15504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1552a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1553a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ImageSpan getMoreChip() {
155422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
155522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
155622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
155722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
155822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
15598faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    private MoreImageSpan createMoreSpan(int count) {
15608faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), count);
15618faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
15628faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
15638faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
15648faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
15658faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                + mMoreItem.getPaddingRight();
15668faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int height = getLineHeight();
15678faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
15688faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Canvas canvas = new Canvas(drawable);
15698faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int adjustedHeight = height;
15708faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Layout layout = getLayout();
15718faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (layout != null) {
15728faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            adjustedHeight -= layout.getLineDescent(0);
15738faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
15748faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
15758faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
15768faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
15778faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        result.setBounds(0, 0, width, height);
15788faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return new MoreImageSpan(result);
15798faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
15808faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
15818faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
15828faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /*package*/ void createMoreChipPlainText() {
15838faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        // Take the first <= CHIP_LIMIT addresses and get to the end of the second one.
15848faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        Editable text = getText();
15858faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int start = 0;
15868faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int end = start;
15878faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        for (int i = 0; i < CHIP_LIMIT; i++) {
15888faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            end = movePastTerminators(mTokenizer.findTokenEnd(text, start));
15898faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            start = end; // move to the next token and get its end.
15908faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
15918faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        // Now, count total addresses.
15928faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        start = 0;
15938faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int tokenCount = countTokens(text);
15948faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT);
15958faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(end, text.length()));
15968faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
15978faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        text.replace(end, text.length(), chipText);
15988faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        mMoreChip = moreSpan;
15998faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
16008faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
16018faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    // Visible for testing.
16028faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    /* package */int countTokens(Editable text) {
16038faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int tokenCount = 0;
16048faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        int start = 0;
16058faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        while (start < text.length()) {
16068faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            start = movePastTerminators(mTokenizer.findTokenEnd(text, start));
16078faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            tokenCount++;
16088faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            if (start >= text.length()) {
16098faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                break;
16108faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            }
16118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
16128faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        return tokenCount;
16138faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira    }
16148faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
16158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1616045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1617045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1618045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
16198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1620a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1621a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void createMoreChip() {
16228faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (mNoChips) {
16238faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            createMoreChipPlainText();
16248faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            return;
16258faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
16268faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
1627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1628bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1629bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
16316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
16326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
16336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
16346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
16356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
16368faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira
163783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
16380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
16390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
16404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
164283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
16434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
16448faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(overage);
1645368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
16464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
16474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
16486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1649368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1650368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
16519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1652368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
16539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1654368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1655368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
16569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
16576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
16586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
16596f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
16606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
166177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1662368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
16634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16648faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        if (totalReplaceEnd < text.length()) {
16658faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            totalReplaceEnd = text.length();
16668faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira        }
166777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
166877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
166977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
16704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
167177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
16720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
16734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
16758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
16768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
16778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
16788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1679a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1680a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /*package*/ void removeMoreChip() {
16814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
16824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
16834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
16844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
16854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
16864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
16874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1688c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
168964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
169064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1691c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1692c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1693c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
169464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
16954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
16964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
16976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
16980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
16990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
17006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
17016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
170264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
170364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
170464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
170564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
170664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
170764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1708bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1709bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1710bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1711bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1712bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
17134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
17144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
17154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
17164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
17174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
17184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1719c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1722b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1724b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1727b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1728b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1729b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1730c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
173100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private RecipientChip selectChip(RecipientChip currentChip) {
17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
17351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
17392f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
17401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1741b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1742b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1743b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1744b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1745b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
17468faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (mNoChips) {
17478faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    return null;
17488faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1752b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1754fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
175683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1757b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
175883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
17598b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
176083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
17621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
17631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1764c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
17651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
17666ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1767b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1768b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
17691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
17701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
17711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
17721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
17731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
17741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
17751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
17761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
17781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
17801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
17821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
17831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
17841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
17871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
17881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
17891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
17916ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
17921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1793fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1795fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
17971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
17981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
17991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
18001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
18011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
18021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1803e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
18041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
18051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
18061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
18071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
18081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
18091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
18101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
18111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
18121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
18131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
18141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
18151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
18161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
18171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
18181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
18191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
18201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1821b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1822b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
18238faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira     * the chip without a delete icon and with an unfocused background. This is
18248faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira     * called when the RecipientChip no longer has focus.
1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
182600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    private void unselectChip(RecipientChip chip) {
1827b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1829b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1830c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1831b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
18328faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira            Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
18335753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
18345753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1835b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1836c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1837b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
18388b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
18398b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
18408faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (!mNoChips) {
18418faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
18428faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
18438faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
18448b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
18458b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
18468b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1847c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1848b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1849b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1850b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1851b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1852c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1853b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1855b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1857b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1858b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1860b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1863b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1865b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1866b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1867b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1868b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
18693656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1870b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1871b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1872b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1873ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
1874ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
1875b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1876b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1877b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1878b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1879b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int toDelete = spanEnd;
1880b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1881b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1882b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1883b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1884c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1885b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        // Always remove trailing spaces when removing a chip.
1886b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1887b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            toDelete++;
1888b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        }
1889b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1890b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        text.delete(spanStart, toDelete);
1891b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1892b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1893c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1894b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1895c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1896b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1897b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1898b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1899b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
190000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    // Visible for testing.
190100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira    /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) {
1902b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1903b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1904b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1905c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1906b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1907b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1908b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1909b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1910b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1911a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (chipText != null) {
1912a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (start == -1 || end == -1) {
1913a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1914a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.insert(0, chipText);
1915a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            } else {
1916a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
1917a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // There may be a space to replace with this chip's new
1918a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // associated
1919a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // space. Check for it
1920a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    int toReplace = end;
1921a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
1922a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
1923a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                        toReplace++;
1924a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    }
1925a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    editable.replace(start, toReplace, chipText);
1926b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                }
1927b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1928c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1929b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1930b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1931b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1932c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1933b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1935b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1936b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1937b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1938b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1939b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1940b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1941b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1942b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1943b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1944b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1945b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1947c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1948b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1950368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1951368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1952368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1953368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1954311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1955311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1956311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1957311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1958311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1959311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1961e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
19631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
19641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
19651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
19661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
19671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
19681e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
19691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
19701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
19711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
19721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
19731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
19741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
19751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
19761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
19771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
19781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
19791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1980368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
19835753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
19845753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
19855753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
1986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1989e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1992054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1993e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1994054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
199576ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1996054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1997054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1998054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1999054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
2000054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
2001054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
2002e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
2003e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
2004e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
2005e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
2006e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
2007e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
2008e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
2009e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
2010e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
20113b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
2012e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
2013e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
2014e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
2015e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
2016e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
2017e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
2018e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
2019e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
20201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
2021e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
2022e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
2023e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
2024e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
202522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
2026e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
2027e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
202877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
20293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
20303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
20313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
20323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void handlePasteClip(ClipData clip) {
20333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeTextChangedListener(mTextWatcher);
20343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
20353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
20363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            for (int i = 0; i < clip.getItemCount(); i++) {
20373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                CharSequence paste = clip.getItemAt(i).getText();
20383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                if (paste != null) {
20393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int start = getSelectionStart();
20403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    int end = getSelectionEnd();
20413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    Editable editable = getText();
20423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    if (start >= 0 && end >= 0 && start != end) {
20433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.append(paste, start, end);
20443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    } else {
20453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                        editable.insert(end, paste);
20463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    }
20473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                    handlePasteAndReplace();
20483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                }
20493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
20503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
20513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
20523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mHandler.post(mAddTextWatcher);
20533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
20543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
205549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    @Override
205649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    public boolean onTextContextMenuItem(int id) {
205749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (id == android.R.id.paste) {
205849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
205949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    Context.CLIPBOARD_SERVICE);
20603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            handlePasteClip(clipboard.getPrimaryClip());
206149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return true;
206249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
206349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return super.onTextContextMenuItem(id);
206449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
206549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
20661e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    private void handlePasteAndReplace() {
20671e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = handlePaste();
20681e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        if (created != null && created.size() > 0) {
20691e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            // Perform reverse lookups on the pasted contacts.
20701e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            IndividualReplacementTask replace = new IndividualReplacementTask();
20711e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            replace.execute(created);
20721e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        }
20731e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    }
20741e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira
207549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
20761e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira    /* package */ArrayList<RecipientChip> handlePaste() {
207749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String text = getText().toString();
207849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
207949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
208049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int tokenStart = originalTokenStart;
208149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int prevTokenStart = tokenStart;
208249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        RecipientChip findChip = null;
20831e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        ArrayList<RecipientChip> created = new ArrayList<RecipientChip>();
208449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenStart != 0) {
208549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            // There are things before this!
208649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            while (tokenStart != 0 && findChip == null) {
208749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                prevTokenStart = tokenStart;
208849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
208949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                findChip = findChip(tokenStart);
209049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
209149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (tokenStart != originalTokenStart) {
209249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (findChip != null) {
209349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = prevTokenStart;
209449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
209549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                int tokenEnd;
209649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                RecipientChip createdChip;
209749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                while (tokenStart < originalTokenStart) {
209849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
209949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
210049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    createdChip = findChip(tokenStart);
210149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    // +1 for the space at the end.
210249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
21031e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                    created.add(createdChip);
210449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
210549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
210649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
210749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Take a look at the last token. If the token has been completed with a
210849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // commit character, create a chip.
210949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (isCompletedToken(lastAddress)) {
211049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            Editable editable = getText();
21111e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
21121e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            commitChip(tokenStart, editable.length(), editable);
21131e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira            created.add(findChip(tokenStart));
211449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
21151e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira        return created;
211649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
211749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
211849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
211949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
212049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd >= length()) {
212149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return tokenEnd;
212249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
212349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
212449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
212549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
212649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
212749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // This token had not only an end token character, but also a space
212849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // separating it from the next token.
212949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
213049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
213149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
213249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return tokenEnd;
213349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
213449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
213677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
21388faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                if (mNoChips) {
21398faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                    return null;
21408faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira                }
214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
214877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
214977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
215077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
215177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
215277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
215377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
215477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
215577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
215677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
21576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
215877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
215977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
216077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
216177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
216277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
216377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
216477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
2165039c9266e72538fd40d382de56807408385fc3daMindy Pereira            RecipientChip chip;
216677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2167039c9266e72538fd40d382de56807408385fc3daMindy Pereira                chip = originalRecipients.get(i);
2168039c9266e72538fd40d382de56807408385fc3daMindy Pereira                if (chip != null) {
2169039c9266e72538fd40d382de56807408385fc3daMindy Pereira                    addresses[i] = createAddressText(chip.getEntry());
2170039c9266e72538fd40d382de56807408385fc3daMindy Pereira                }
217177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
217277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
217377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
217477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
217577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
217677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
21771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
217877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
217977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
21801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
21811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
218277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
218377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
218477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
218577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
218677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
218777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
218877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
218977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
219077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
219177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
219277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
219377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
219477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
219577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
219677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
219777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
219877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
219977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
220077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
2201b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
22022e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                                oldText.removeSpan(chip);
2203b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
22046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
22056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
2206b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
22076f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
220877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
220977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
221077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
221177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
22122e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                        setText(text);
221377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
221477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
221577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
221677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
221777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
221877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
221977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
222077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
222177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
222277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
222377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
222477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
222577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
222677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
222777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
222877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
222977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
2230039c9266e72538fd40d382de56807408385fc3daMindy Pereira            RecipientChip chip;
223177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2232039c9266e72538fd40d382de56807408385fc3daMindy Pereira                chip = originalRecipients.get(i);
2233039c9266e72538fd40d382de56807408385fc3daMindy Pereira                if (chip != null) {
2234039c9266e72538fd40d382de56807408385fc3daMindy Pereira                    addresses[i] = createAddressText(chip.getEntry());
2235039c9266e72538fd40d382de56807408385fc3daMindy Pereira                }
223677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
223777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
223877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
223977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
22401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
224177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
224277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
22431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
22441e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase()));
224577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
224677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
224777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
224877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
224977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
225077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
225177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
225277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
225377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
225477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
225577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
225677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
225777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
2258b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
22596f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
22606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
22616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
22626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
22636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
22646f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
22656f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
22666f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
22676f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
22686f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
22696f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
2270b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2271b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
2272b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2273b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2274b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2275b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2276b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2277b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2278b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2279b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2280b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2281b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2282b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
2283b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
2284b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
2285b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2286b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
2287b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
2288b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
2289b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
2290b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
22913e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            if (mDragEnabled) {
22923e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Start drag-and-drop for the selected chip.
22933e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                startDrag(currentChip);
22943e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            } else {
22953e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Copy the selected chip email address.
22963e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                showCopyDialog(currentChip.getEntry().getDestination());
22973e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            }
22983e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
22993e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23003e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23013e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23023e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Enables drag-and-drop for chips.
23033e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23043e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public void enableDrag() {
23053e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        mDragEnabled = true;
23063e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23073e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23083e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23093e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Starts drag-and-drop for the selected chip.
23103e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23113e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private void startDrag(RecipientChip currentChip) {
23123e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        String address = currentChip.getEntry().getDestination();
23133e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
23143e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23153e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Start drag mode.
23163e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
23173e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23183e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // Remove the current chip, so drag-and-drop will result in a move.
23193e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        // TODO (phamm): consider readd this chip if it's dropped outside a target.
23203e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        removeChip(currentChip);
23213e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23223e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23233e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23243e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Handles drag event.
23253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    @Override
23273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    public boolean onDragEvent(DragEvent event) {
23283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        switch (event.getAction()) {
23293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_STARTED:
23303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                // Only handle plain text drag and drop.
23313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
23323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DRAG_ENTERED:
23333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                requestFocus();
23343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
23353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            case DragEvent.ACTION_DROP:
23363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                handlePasteClip(event.getClipData());
23373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham                return true;
23383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
23393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        return false;
23403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    }
23413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    /**
23433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     * Drag shadow for a {@link RecipientChip}.
23443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham     */
23453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham    private final class RecipientChipShadow extends DragShadowBuilder {
23463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        private final RecipientChip mChip;
23473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public RecipientChipShadow(RecipientChip chip) {
23493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip = chip;
23503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
23513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
23533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
23543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            Rect rect = mChip.getDrawable().getBounds();
23553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowSize.set(rect.width(), rect.height());
23563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            shadowTouchPoint.set(rect.centerX(), rect.centerY());
23573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        }
23583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham
23593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        @Override
23603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham        public void onDrawShadow(Canvas canvas) {
23613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham            mChip.getDrawable().draw(canvas);
2362b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2363b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2364b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2365b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
2366b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
2367b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
2368b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
2369b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
2370b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
2371b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
2372b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
2373b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
2374b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2375b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2376b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2377b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2378b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2379b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2380b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2381b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2382b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2383b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
2384b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2385b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2386b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2387b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2388b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2389b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2390b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2391b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2392b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2393b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2394b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2395b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2396b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2397b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2398b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2399b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2400b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2401b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2402b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2403b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2404b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2405b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
240677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2407