RecipientEditTextView.java revision 49ce9866ae314b3513e17008f107ceded23f9cf0
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/*
22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project
32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License.
62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at
72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira *
102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software
112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and
142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License.
152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips;
182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
19b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.app.Dialog;
20b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipData;
2149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereiraimport android.content.ClipDescription;
22b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager;
232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context;
24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface;
25b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener;
26c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap;
271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory;
28c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix;
30c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
3477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask;
35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler;
36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message;
3722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable;
382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
39572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
47c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
53c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
55a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
56b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
57c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
58c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
62c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
63572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
64416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
67cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable;
68b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
69c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
70156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
712d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
72e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow;
73416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
74c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
75b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
76a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
78b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
8177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
89b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
90b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
91e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
92e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        PopupWindow.OnDismissListener {
93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
95c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
117c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
119054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
120a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
123e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
124e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
127045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
128045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
133156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
134156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
135c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
136c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
13777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
14005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
14105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
142c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
143e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
144dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
15077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
154bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
166a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
172e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
175416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
176416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
186e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
197a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
198a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    private Runnable mDelayedShrink = new Runnable() {
199a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
200a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        @Override
201a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        public void run() {
202a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            shrink();
203a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        }
204a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    };
206a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
2072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
20977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
21077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
21177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
213e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
215e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2160436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
221368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
222e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
226368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
232c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
234156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
235156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
236156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
237156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
239156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
240156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
241156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
243156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2446ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2456ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
246b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
247ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
248cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
249ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
250ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
251ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
252ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
253ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
254ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
26183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
26283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
26383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
26483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2664fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
269d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
270d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
271d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
272d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
27322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    @Override
27422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    public void onRestoreInstanceState(Parcelable state) {
27522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (!TextUtils.isEmpty(getText())) {
27622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(null);
27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        } else {
27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(state);
27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
28122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
2827c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira
2837c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    public Parcelable onSaveInstanceState() {
2847c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
2857c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        clearSelectedChip();
2867c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira        return super.onSaveInstanceState();
2877c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira    }
2887c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira
289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
291c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
292c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
295c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
296c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
297001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
298001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
299001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
300001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
3034f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
30405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
307c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
308a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
311a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
312a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
313a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
314a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
3156ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
316c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
317c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
318d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
319d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
320b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
321d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
3224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
3234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
3244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
325416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
3264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
329b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
330b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
331b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
332b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
333b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3355a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3365a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
339a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            if (getWidth() <= 0) {
340a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
341a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // and there is no reason to attempt to commit chips yet.
342a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // This focus lost must be the result of an orientation change
343a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // or an initial rendering.
344a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // Re-post the shrink for later.
345a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
346a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.post(mDelayedShrink);
347a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                return;
348a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
349001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
350001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
351001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
352a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
353001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
354001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
355001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
356001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    Editable text = getText();
360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
361a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    // This token was already tokenized, so skip past the ending token.
3628a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                        whatEnd++;
364a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    }
365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
366001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
367a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int selEnd = getSelectionEnd();
368a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) {
369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
371001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
372001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
373001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
374090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
375001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
38577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
38677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
38777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
38877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
38977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
39077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
394e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
395c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
396c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
397c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
398c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
399c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
402e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
42377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
42497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
42597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
42697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
428f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
429f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
430f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
431f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
432f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
433f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
442045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
443045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
444045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
445b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
446b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                mChipBackground : mInvalidChipBackground;
447045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
448045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
449e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
453e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
4554221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
4564221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
4574221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
4584221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
4591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4601a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4611a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
469045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
470045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
471045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
472045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4749024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4769024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
47990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
48090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
48190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
48290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
48390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
48490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
48590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                if (photo != null) {
495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Rect backgroundPadding = new Rect();
497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    mChipBackground.getPadding(backgroundPadding);
498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            0 + backgroundPadding.top,
500ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            width - backgroundPadding.right,
501ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            height - backgroundPadding.bottom);
502ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Matrix matrix = new Matrix();
503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
504ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
505ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                }
506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
5079024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
5089024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
510e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
51197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
512379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
51397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
5151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
5171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
5181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
52097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
52197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
52297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        paint.getTextBounds((String)text, 0, text.length(), bounds);
52397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
52497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
52597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
52697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
5291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
532c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
534c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
53777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
541e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
542c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
544045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
55077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
552c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
55377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5562d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
558045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
559045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5605519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5615519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
563c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5645519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
565416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
56697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
56797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
568f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
569f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5782d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5804f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5814f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5824f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5844f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5871426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5891426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
590c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
591b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
592b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
59443876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
595045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
596d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
597b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
610b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
611a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
612a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
613a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        mMoreItem = moreItem;
614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    }
615a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
616ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
617ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
618ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
619ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipBackground = chipBackground;
620ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
621ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
622ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipHeight(int height) {
624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipHeight = height;
625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
626ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
628bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
629bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
631bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
632bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
633bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
637bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
638c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
639c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
640c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
64153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (width != 0 && height != 0) {
64253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            if (mPendingChipsCount > 0) {
64353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                postHandlePendingChips();
64453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            } else {
64553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                checkChipWidths();
64653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
6477bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
64877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
649416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
650416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
651416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
652416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
653416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
654416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
655416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
656416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
658416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
659c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
660c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
661a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
662a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
663a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
664a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
665a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
66653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void checkChipWidths() {
66753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        // Check the widths of the associated chips.
66853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (chips != null) {
67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            Rect bounds;
67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            for (RecipientChip chip : chips) {
67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                bounds = chip.getDrawable().getBounds();
67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    // Need to redraw that chip.
67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    replaceChip(chip, chip.getEntry());
67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                }
67753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
678a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    }
68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void handlePendingChips() {
6827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
6837bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
6847bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
6857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
6867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
6877bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
68953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (mPendingChipsCount <= 0) {
69053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            return;
69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        }
69253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
694a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
695a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
7080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
7100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
711a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
71222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
714a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
715a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
719a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
721a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
72277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
725a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
726a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
72722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
728a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
72977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
73077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
732a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
7380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
74522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
7490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
7701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
7711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
7721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
7731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
7741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
775b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
7761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
7780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
781d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
782a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            String destText = createDisplayText(entry);
783d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
784b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            int textLength = destText.length() - 1;
785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
786d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
788d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
792d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
79822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
79922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
80022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
8016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
803d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
80477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
8050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
8070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
808d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
809d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
810d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
8110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
8121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
8136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
814454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
815454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
8171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
8181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
8196ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
8206ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
8216ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
8226ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
8236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
8241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
8251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
8266ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
8276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
828a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
8296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
831a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
833a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
834a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
837a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
838a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
845490556a764a879cd0eaff358e90705cc1335c92eErik                }
846d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
8476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
848454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
849a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
850a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
8511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
8521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
8536ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
8546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
8566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
8571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
8581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
8591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
8601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
8610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
8630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
865c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
866c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
869c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
882c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
883c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
884c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
885c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
886c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
89195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
89295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
89395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
89495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
89595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
89695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
89795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
8988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
90695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
907c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
908c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
909c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
912d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
915e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
916e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
917e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
918e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
919e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
92295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
926e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
927e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
929e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
939e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
947045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
948045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
949045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
950045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
951045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
952045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
953045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
9548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
9554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
9574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
958dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
959e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
961e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
9664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
9784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
979054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
9814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
98349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        ListAdapter adapter = getAdapter();
98449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
98549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                && end == getSelectionEnd()) {
986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
989e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
99249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (editable.length() > tokenEnd + 1) {
99349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
99449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
99549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd++;
99649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
997a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
998b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
1000e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
10011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1002d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
1003d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1004d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
100571fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
10065ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
10075ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
1008d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
100949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
101049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // just committed.
101149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // For paste, it may not be as there are possibly multiple
101249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                // tokens being added.
101349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (end == getSelectionEnd()) {
101449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    dismissDropDown();
101549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
1016f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
10174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
1018d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
1019d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
10204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
1021d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
1022d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
10234031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10244031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
1025f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
10264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
1027f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
1028f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
1029f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
1030f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
1034f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1037c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
10384031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1039c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
1040c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
1041c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
1042c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
1043f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
1044f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
1045f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1046a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking != end) {
1047f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
1048f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1049f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
1050f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
1052e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
10531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
10541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
10551e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
10561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
10571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
10581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
10591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
106005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
1062e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1063e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1064e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
106539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
106639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
106739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
106839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
106939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
1070e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
1071e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
1072e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1073e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
1074e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
10753b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
10763b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
10773b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
10783b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
1079a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (chipText != null) {
1080a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.replace(start, getSelectionEnd(), chipText);
1081a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            }
10823b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
1083054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
108405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
108505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
10868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
10888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
10898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1093b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1094b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
11042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
11052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
11064031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
11074031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1108ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1111b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1112b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1113b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1114b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1117b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
112849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (enoughToFilter() && !isCompletedToken(text)) {
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
114249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
114349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
114449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (TextUtils.isEmpty(text)) {
114549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return false;
114649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
114749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Check to see if this is a completed token before filtering.
114849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int end = text.length();
114949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
115049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String token = text.toString().substring(start, end).trim();
115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (!TextUtils.isEmpty(token)) {
115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return false;
115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
116336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
11688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
11698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
11718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
11728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
11738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1176d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1177d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1178d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1179d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1181d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
11836caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
11860436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1195b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
11978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
11988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1205416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
12065753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
12075753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
12085753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1217416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1218416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1219c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1220416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1221416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1222416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1226c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1230e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
123177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1232416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1234368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1235e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1236e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1244b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1250e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1251e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1252e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1253e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1254e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1255e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1258b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1259b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1260b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
12611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
12621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
12631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
12641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
12651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1266a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1270b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1272e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1276c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1277c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1278c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12924fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
12934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
12944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
12974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1298c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1301c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
13064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
13074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
13084fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
13094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
13104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
13114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1313c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1317b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1318b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1319b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
13274031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ String createDisplayText(RecipientEntry entry) {
13288659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
13298659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
13308659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
13318659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
13328659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1333a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1334a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1335a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1336c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1337c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1338c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1339c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1340a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
13418659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
13428659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String displayText = token.toString();
13438659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String trimmedDisplayText = displayText.trim();
13448659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
13458659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
13468659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                .terminateToken(displayText) : displayText;
13478659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
13488659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1349fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
13508659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String displayText = createDisplayText(entry);
1351a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
1352a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            return null;
1353a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        }
1354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1355b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int textLength = displayText.length()-1;
1356c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1357c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
13606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
13616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1362c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
13636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1364c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1365c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1367c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1369c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1370c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1371c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
13748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
13758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1376c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1378c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1380c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
13820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
13830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
13841e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
13851e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
13861e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
13944221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
13954221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
13964221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
13974221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1398f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
14020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
14030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
14040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
14060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
14070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
14080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
14090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1410c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1411c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
14120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
14130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
14140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
14150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
14160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
14170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
14180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
14190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
14237a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
14247a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
14257a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
14267a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
14277a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1430c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1431c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
143283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
143383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
143483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
143583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
14364031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
14374031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ RecipientChip[] getSortedRecipients() {
14386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
14396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                .asList(getRecipients()));
14406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
14416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
14426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
14436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
14446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
14456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
14466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
14476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
14486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
14496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
14506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
14516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
14526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
14536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
14546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
14556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
14566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
14576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
14586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
146283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
14637a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
14647a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
14657a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
14667a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1467c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
14744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
14754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
14794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
14834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
14844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
14878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
14888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
14894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
14914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
14924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1494a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1495a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ImageSpan getMoreChip() {
149622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
149722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
149822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
149922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
150022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
15018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1502045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1503045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1504045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
15058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1506a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1507a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void createMoreChip() {
1508bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1509bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1510bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1511bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
15126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
15136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
15146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
15156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
15166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
151783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
15180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
15216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
152283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
15234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1524c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1525c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1526c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1527c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1528c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1529c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
15304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
15314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
15324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
153322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        int adjustedHeight = height;
153422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        Layout layout = getLayout();
153522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (layout != null) {
153622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            adjustedHeight -= layout.getLineDescent(0);
153722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
153822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
15394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
15414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
15426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
15434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1544368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
15454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
15460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
15470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1550368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
15514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
15524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
15536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1554368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1555368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
15569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1557368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
15589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1559368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1560368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
15619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
15626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
15636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
15646f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
15656f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
156677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1567368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
156977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
157077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
157177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
15724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
157377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
15740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
15754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
15788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
15798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
15808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1581a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1582a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /*package*/ void removeMoreChip() {
15834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
15844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
15854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
15864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
15874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
15884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
15894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1590c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
159164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
159264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1593c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1594c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1595c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
159664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
15974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
15984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
15996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
16000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
16010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
16026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
16036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
160464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
160564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
160664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
160764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
160864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
160964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1610bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1611bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1612bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1613bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1614bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
16154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
16164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
16174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
16184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
16194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
16204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1621c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1622b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1623b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1624b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1625b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1627b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1628b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1629b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1630b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1631b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1633b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
16341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
16351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
16361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
16371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
16381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
16391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
16401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
16412f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
16421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1643b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1644b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1645b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1646b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1647b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1648b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1649b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1653fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
165583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1656b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
165783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
16588b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
165983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1660b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
16611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
16621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1663c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
16656ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1666b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1667b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
16691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
16711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
16721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
16731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
16741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
16761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
16771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
16791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
16801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
16811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
16821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
16831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
16841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
16851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
16861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
16871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
16881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
16891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
16906ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
16911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1692fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1693b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1694fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1695c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
16961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
16981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
16991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
17001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
17011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1702e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
17061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
17111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
17131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
17151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
17161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
17171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
17181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1722b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1724b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1727b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1728b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1729c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1730b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
17315753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            Log.w(TAG,
17325753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    "The chip doesn't exist or may be a chip a user was editing");
17335753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
17345753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1735b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1736c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1737b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
17388b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
17398b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
17408b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
17418b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17428b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
17438b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17448b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1745c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1746b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1750c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1752c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1757b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1759ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1764b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1765c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1766c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1767b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1768b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1769b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1771b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1772b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1773b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1774b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1775b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1778b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
17813656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1785ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
1786ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1791b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int toDelete = spanEnd;
1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1795b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1797b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        // Always remove trailing spaces when removing a chip.
1798b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1799b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            toDelete++;
1800b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        }
1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1802b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        text.delete(spanStart, toDelete);
1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1804b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1806b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1808b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1809b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1810b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1811b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1814b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1815b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1817b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1819b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1821b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1822a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira        if (chipText != null) {
1823a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            if (start == -1 || end == -1) {
1824a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
1825a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                editable.insert(0, chipText);
1826a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira            } else {
1827a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
1828a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // There may be a space to replace with this chip's new
1829a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // associated
1830a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    // space. Check for it
1831a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    int toReplace = end;
1832a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
1833a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
1834a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                        toReplace++;
1835a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    }
1836a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira                    editable.replace(start, toReplace, chipText);
1837b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                }
1838b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1840b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1841b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1842b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1843c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1844b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1845c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1846b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1847b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1848b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1849b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1850b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1851b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1852b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1853b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1854b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1855b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1857c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1858c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1860c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1861368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1862368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1863368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1864368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1865311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1866311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1867311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1868311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1869311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1870311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
18741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
18751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
18761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
18771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
18781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
18791e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
18801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
18811e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
18821e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
18831e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
18841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
18851e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
18861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
18871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
18881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
18891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
18901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1891368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
18945753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
18955753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
18965753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
1897e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1899e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1903054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1905054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
190676ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1907054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1908054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1909054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1910054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1911054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1912054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1921e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
19223b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
1923e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1924e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1925e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1926e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1927e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1928e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1929e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1930e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
19311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1932e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
193622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
1937e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
193977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
194049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    @Override
194149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    public boolean onTextContextMenuItem(int id) {
194249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (id == android.R.id.paste) {
194349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            removeTextChangedListener(mTextWatcher);
194449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
194549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    Context.CLIPBOARD_SERVICE);
194649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            ClipData clip = clipboard.getPrimaryClip();
194749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (clip != null
194849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
194949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                for (int i = 0; i < clip.getItemCount(); i++) {
195049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    CharSequence paste = clip.getItemAt(i).getText();
195149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    if (paste != null) {
195249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        int start = getSelectionStart();
195349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        int end = getSelectionEnd();
195449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        Editable editable = getText();
195549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        if (start >= 0 && end >= 0 && start != end) {
195649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                            editable.append(paste, start, end);
195749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        } else {
195849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                            editable.insert(end, paste);
195949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        }
196049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                        handlePaste();
196149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    }
196249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
196349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
196449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            mHandler.post(mAddTextWatcher);
196549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return true;
196649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
196749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return super.onTextContextMenuItem(id);
196849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
196949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
197049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
197149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */void handlePaste() {
197249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String text = getText().toString();
197349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
197449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
197549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int tokenStart = originalTokenStart;
197649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        int prevTokenStart = tokenStart;
197749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        RecipientChip findChip = null;
197849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenStart != 0) {
197949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            // There are things before this!
198049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            while (tokenStart != 0 && findChip == null) {
198149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                prevTokenStart = tokenStart;
198249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
198349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                findChip = findChip(tokenStart);
198449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
198549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            if (tokenStart != originalTokenStart) {
198649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                if (findChip != null) {
198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = prevTokenStart;
198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                int tokenEnd;
199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                RecipientChip createdChip;
199149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                while (tokenStart < originalTokenStart) {
199249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
199349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
199449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    createdChip = findChip(tokenStart);
199549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    // +1 for the space at the end.
199649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
199749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                }
199849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            }
199949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
200049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // Take a look at the last token. If the token has been completed with a
200149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // commit character, create a chip.
200249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (isCompletedToken(lastAddress)) {
200349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            Editable editable = getText();
200449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            commitChip(editable.toString().indexOf(lastAddress, originalTokenStart), editable
200549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira                    .length(), editable);
200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
200749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
200949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    // Visible for testing.
201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd >= length()) {
201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            return tokenEnd;
201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
201449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // This token had not only an end token character, but also a space
201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        // separating it from the next token.
202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira            tokenEnd++;
202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        }
202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira        return tokenEnd;
202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira    }
202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira
202677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
202777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
202877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
202977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
203077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
203177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
203277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
203377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
203477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
203577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
203677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
203777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
203877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
203977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
204077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
204177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
204277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
204377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
204477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
20456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
204677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
204777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
204877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
204977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
205077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
205177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
205277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
205377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
20548659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                addresses[i] = createDisplayText(originalRecipients.get(i).getEntry());
205577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
205677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
205777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
205877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
205977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
206077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
20611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
206277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
206377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
20641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
20651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
207177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
207477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
207577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
208077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
2085b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
20862e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                                oldText.removeSpan(chip);
2087b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
20886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
20896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
2090b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
20916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
209477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
209577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
20962e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira                        setText(text);
209777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
209877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
209977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
210077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
210177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
210477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
210577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
210677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
21158659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                addresses[i] = createDisplayText(originalRecipients.get(i).getEntry());
211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
21201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
212177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
212277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
21231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
21241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
212577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
212677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
212777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
212877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
212977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
213077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
213177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
213377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
213477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
213677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
2138b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
21396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
21406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
21416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
21426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
21436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
21446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
21456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
21466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
21476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
21486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
21496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
2150b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2151b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
2152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
2163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
2164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
2165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2166b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
2167b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
2168b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
2169b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
2170b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
2171b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
2172b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
2173b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2174b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2175b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2176b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
2177b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
2178b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
2179b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
2180b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
2181b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
2182b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
2183b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
2184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
2185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2186b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2187b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2206b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2207b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2208b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2209b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2210b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2211b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2212b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2213b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2214b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2215b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2216b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
221777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2218