RecipientEditTextView.java revision 71fa24bfb96744cf28d368d58172c14638e14693
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;
21b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager;
222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context;
23b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface;
24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener;
25c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap;
261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory;
27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas;
281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix;
29c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect;
301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF;
31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable;
3377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask;
34156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler;
35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message;
3622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable;
372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
38572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
46c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
512d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
52c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
54a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
55b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
56c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
57c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
62572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
63416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
66cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable;
67b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
69156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
71e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow;
72416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
73c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
74b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
75a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
77b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
8077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
88b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
89b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
90e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
91e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        PopupWindow.OnDismissListener {
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
116c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
118054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
119a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
123e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
126045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
127045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
133156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
134c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
135c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
13677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
14005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
141c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
142e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
143dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
152bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
164a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
175416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
197a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    private Runnable mDelayedShrink = new Runnable() {
198a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
199a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        @Override
200a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        public void run() {
201a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            shrink();
202a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        }
203a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
204a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    };
205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
2062d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2072d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
20877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
20977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
21077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
212e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
214e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2150436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
216b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
220368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
221e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
222b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
225368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
226b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
231c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
233156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
234156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
235156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
236156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
238156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
239156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
240156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
241156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2436ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2446ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
245b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
246ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
247cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
248ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
249ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
250ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
251ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
252ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
253ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
26083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
26183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
26283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
26383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
266b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2674fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
268d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
269d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
270d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
271d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
27222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    @Override
27322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    public void onRestoreInstanceState(Parcelable state) {
27422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (!TextUtils.isEmpty(getText())) {
27522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(null);
27622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        } else {
27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            super.onRestoreInstanceState(state);
27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
281c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
282c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
283c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
284c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
285c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
286c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
287c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
288c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
289001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
290001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
291001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
292001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
2954f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
29605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
297c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
298c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
300a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
303a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
304a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
305a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
306a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
3076ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
308c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
310d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
311d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
312b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
313d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
3144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
3154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
3164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
317416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
3184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
321b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
322b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
323b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
324b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
325b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3275a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3285a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
331a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            if (getWidth() <= 0) {
332a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
333a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // and there is no reason to attempt to commit chips yet.
334a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // This focus lost must be the result of an orientation change
335a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // or an initial rendering.
336a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                // Re-post the shrink for later.
337a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
338a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                mHandler.post(mDelayedShrink);
339a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                return;
340a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
341001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
342001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
343001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
344a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
345001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
346001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
347001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
348001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
349001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
350001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
351a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    Editable text = getText();
352a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
353a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    // This token was already tokenized, so skip past the ending token.
3548a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
355a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                        whatEnd++;
356a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    }
357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    int selEnd = getSelectionEnd();
360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                    if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) {
361001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
362001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
363001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
364001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
366090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
367001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
37777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
37877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
37977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
38077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
38177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
38277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
386e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
387c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
388c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
389c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
390c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
391c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
394e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
398e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3991e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
4011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
4021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
41577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
41697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
41797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
41897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
420f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            Rect backgroundPadding = new Rect();
421f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
422f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
423f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    0 + backgroundPadding.top,
424f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    width - backgroundPadding.right,
425f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira                    height - backgroundPadding.bottom);
4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
4281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
433045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
434045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
435045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
436045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
437b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
438b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                mChipBackground : mInvalidChipBackground;
439045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
440045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
441e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
442c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
445e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
4474221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
4484221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
4494221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
4504221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4521a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4531a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
461045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
462045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
463045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
464045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4689024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
46990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
47090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
47190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
47290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
47390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
47490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
47690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4789024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
486ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                if (photo != null) {
487ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
488ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Rect backgroundPadding = new Rect();
489ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    mChipBackground.getPadding(backgroundPadding);
490ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
491ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            0 + backgroundPadding.top,
492ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            width - backgroundPadding.right,
493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                            height - backgroundPadding.bottom);
494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    Matrix matrix = new Matrix();
495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira                }
498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4999024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
5009024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
502e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
50397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
504379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
50597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
5071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
5091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
5101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
51297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
51397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
51497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        paint.getTextBounds((String)text, 0, text.length(), bounds);
51597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
51697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
51797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
51897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
5191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
5211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
5221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
5231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
5251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
526c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
52977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
5321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
533e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
534c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
536045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
538c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
54277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
543c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
54577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5472d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5482d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
550045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
551045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5525519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5535519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
555c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5565519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
557416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
55897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
55997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
560f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
561f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
571c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5724f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5734f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5744f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5764f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5791426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5811426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
583b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
584b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
58643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
587045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
588d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
589b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
590b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
591b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
592b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
593b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
594b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
595b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
596b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
597b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
603a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
604a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
605a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira        mMoreItem = moreItem;
606a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    }
607a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira
608ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
609ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
610ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
611ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipBackground = chipBackground;
612ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
613ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
614ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
615ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /* package */ void setChipHeight(int height) {
616ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira        mChipHeight = height;
617ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    }
618ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
619bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
620bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
621bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
622bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
623bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
624bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
625bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
626bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
628bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
629bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
630c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
631c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
632c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
63353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (width != 0 && height != 0) {
63453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            if (mPendingChipsCount > 0) {
63553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                postHandlePendingChips();
63653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            } else {
63753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                checkChipWidths();
63853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
6397bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
64077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
641416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
642416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
643416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
644416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
645416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
646416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
647416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
648416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
649416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
650416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
651c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
652c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
653a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
654a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
655a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
656a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
657a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
65853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void checkChipWidths() {
65953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        // Check the widths of the associated chips.
66053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
66153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (chips != null) {
66253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            Rect bounds;
66353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            for (RecipientChip chip : chips) {
66453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                bounds = chip.getDrawable().getBounds();
66553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
66653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    // Need to redraw that chip.
66753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                    replaceChip(chip, chip.getEntry());
66853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira                }
66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            }
670a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    }
67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira    private void handlePendingChips() {
6747bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
6757bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
6767bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
6777bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
6787bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
6797bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        if (mPendingChipsCount <= 0) {
68253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira            return;
68353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira        }
68453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira
685a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
686a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
687a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
688a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
689a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
690a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
691a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
692a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
694a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
695a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
7000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
7020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
70422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
710a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
711a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
71477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
715a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
71922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                // to showing addresses for all of them.
720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
72177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
72277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
7250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
7300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
7320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
73722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            mMoreChip = getMoreChip();
7380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
7450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
7490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
7621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
7631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
7641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
7651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
7661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
767b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
7681e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
7700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
7710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
773d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
774a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            String destText = createDisplayText(entry);
775d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
776b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            int textLength = destText.length() - 1;
777d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
778d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
779d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
780d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
781d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
782d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
783d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
784d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
786d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
788d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
79022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                if (mTemporaryRecipients == null) {
79122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
79222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                }
7936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
79677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
7970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
800d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
801d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
8030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
8041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
8056ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
806454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
807454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
8081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
8091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
8101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
8116ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
8126ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
8136ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
8146ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
8156ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
8171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
8186ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
8196ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
820a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        String validatedToken = null;
8216ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8226ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
823a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            validatedToken = mValidator.fixText(token).toString();
824a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
825a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                if (validatedToken.contains(token)) {
826a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // protect against the case of a validator with a null domain,
827a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // which doesn't add a domain to the token
828a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
829a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    if (tokenized.length > 0) {
830a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                        validatedToken = tokenized[0].getAddress();
831a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    }
832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                } else {
833a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // We ran into a case where the token was invalid and removed
834a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // by the validator. In this case, just use the original token
835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    // and let the user sort out the error chip.
836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                    validatedToken = null;
837490556a764a879cd0eaff358e90705cc1335c92eErik                }
838d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
8396ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
840454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira        return RecipientEntry
842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
8431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
8441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
8456ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
8466ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
8476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
8486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
8491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
8501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
8511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
8521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
8530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
8541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
8550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
8560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
857c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
858c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
859c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
860c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
861c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
862c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
8648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
8658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
8668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
8678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
8688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
8698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
874c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
875c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
876c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
877c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
878c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
882c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
88395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
88495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
88595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
88695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
88795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
88895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
88995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
8908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
8918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
8928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
8938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
8948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
8958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
8968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
8978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
89895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
899c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
900c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
901c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
902c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
903c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
904d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
905c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
906c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
907e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
908e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
909e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
910e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
911e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
912e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
91495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
915e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
916e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
917e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
918e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
919e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
921e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
922e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
926c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
927c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
928c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
934e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
935e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
936e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
937e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
939045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
940045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
941045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
942045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
943045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
944045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
945045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
9468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
9474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
948045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
9494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
950dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
951e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
952e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
953e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
954e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
955e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
956e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
957e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
9584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
959e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
961e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
966e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
9704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
971054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
9734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
975001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
982a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            if (editable.length() > tokenEnd && editable.charAt(tokenEnd) == ',') {
983a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira                tokenEnd++;
984a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira            }
985b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
9881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
989d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
990d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
991d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
99271fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
9935ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                        editable.replace(start, end, chipText);
9945ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira                    }
995d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
996d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
997f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                sanitizeBetween();
9984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
999d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
1000d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
10014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
1002d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
1003d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
10044031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10054031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ void sanitizeBetween() {
1006f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        // Find the last chip.
10074031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
1008f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        if (recips != null && recips.length > 0) {
1009f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip last = recips[recips.length - 1];
1010f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            RecipientChip beforeLast = null;
1011f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (recips.length > 1) {
1012f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                beforeLast = recips[recips.length - 2];
1013f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1014f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int startLooking = 0;
1015f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            int end = getSpannable().getSpanStart(last);
1016f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            if (beforeLast != null) {
1017f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1018c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                Editable text = getText();
10194031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1020c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    // There is nothing after this chip.
1021c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                    return;
1022c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                }
1023c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
1024f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                    startLooking++;
1025f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                }
1026f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1027a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking != end) {
1028f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira                getText().delete(startLooking, end);
1029f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira            }
1030f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        }
1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira    }
1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira
1033e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
10341e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
10351e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
10361e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
10371e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
10381e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
10391e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
10401e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
104105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
10421e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
1043e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
1044e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1045e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
104639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        if (start == -1 || end == -1) {
104739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            // This chip no longer exists in the field.
104839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            dismissDropDown();
104939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira            return;
105039f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira        }
1051e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
1052e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
1053e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
1054e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
1055e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
10563b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        if (!TextUtils.isEmpty(text)) {
10573b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
10583b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
10593b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            CharSequence chipText = createChip(entry, false);
10603b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira            editable.replace(start, getSelectionEnd(), chipText);
10613b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira        }
1062054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
106305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
106405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
10658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
10668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
10678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
10688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1069c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1070c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
1071b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1073b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
1074b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1075b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1077c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1079c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
1080c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1081c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
10832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
10842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
10854031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
10864031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ Spannable getSpannable() {
1087ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1090b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
1091b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1093b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1094b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1097b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
112636d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
11318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
11328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
11338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
11348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
11358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
11368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1139d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1140d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1141d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1142d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1144d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
11466caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1147b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1148b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
11490436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1158b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
11608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
11618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1162b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1168416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
11695753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            } else if (mSelectedChip != null
11705753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
11715753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                chipWasSelected = true;
1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1180416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1181416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1182c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1183416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1184416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1185416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1186b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1187b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1188b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1189c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1190b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1191b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1192b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1193e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
119477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1195416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1196b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1197368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1198e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1199e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1200b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1202b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1205b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1213e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1214e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1215e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1216e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1217e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1218e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1221b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1222b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
12241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
12251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
12261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
12271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
12281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1229a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1234b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1235e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1236b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1243c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1244c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1245c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1246c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1251c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1253c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1254c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
12564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
12574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
12604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1261c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1264c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1265c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1266c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1267c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
12694fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
12704fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
12714fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
12724fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
12734fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
12744fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1276c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1277c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1278c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1280b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1281b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1282b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12894031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
12904031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ String createDisplayText(RecipientEntry entry) {
12918659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String display = entry.getDisplayName();
12928659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String address = entry.getDestination();
12938659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
12948659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira            display = null;
12958659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        }
1296a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        if (address != null) {
1297a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // Tokenize out the address in case the address already
1298a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira            // contained the username as well.
1299c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1300c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1301c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                address = tokenized[0].getAddress();
1302c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira            }
1303a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira        }
13048659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
13058659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String displayText = token.toString();
13068659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String trimmedDisplayText = displayText.trim();
13078659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        int index = trimmedDisplayText.indexOf(",");
13088659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
13098659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                .terminateToken(displayText) : displayText;
13108659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira    }
13118659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira
1312fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
13138659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira        String displayText = createDisplayText(entry);
1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1315b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int textLength = displayText.length()-1;
1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1317c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
13206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
13216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
13236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1326c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1330c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
13348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
13358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1336c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1337c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1338c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1339c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1340c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1341c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
13420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
13430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
13441e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
13451e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
13461e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1347c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
13544221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
13554221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
13564221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
13574221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1358f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira        sanitizeBetween();
1359c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1360c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
13610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
13620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
13630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
13640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
13650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
13660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
13670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
13680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
13690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1370c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1371c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
13720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
13730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
13740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
13750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
13760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
13770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
13780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
13790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1380c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1382c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
13837a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
13847a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
13857a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
13867a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
13877a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
139283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
139383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
139483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
139583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
13964031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    // Visible for testing.
13974031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira    /* package */ RecipientChip[] getSortedRecipients() {
13986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
13996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                .asList(getRecipients()));
14006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
14016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
14026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
14036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
14046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
14056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
14066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
14076f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
14086f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
14096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
14106f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
14116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
14126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
14136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
14146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
14156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
14166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
14176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
14186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1419c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
142283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
14237a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
14247a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
14257a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
14267a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1430c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
14344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
14354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
14394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
14434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
14444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
14468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
14478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
14488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
14494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
14504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
14514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
14524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1454a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1455a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ImageSpan getMoreChip() {
145622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
145722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira                MoreImageSpan.class);
145822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
145922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira    }
146022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira
14618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1462045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1463045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1464045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
14658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1466a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1467a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /* package */ void createMoreChip() {
1468bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1469bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1470bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1471bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
14726f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
14736f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
14746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
14756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
14766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
147783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
14780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
14790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
14804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
14816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
148283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
14834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1484c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1485c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1486c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1487c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1488c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1489c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
14904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
14914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
14924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
149322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        int adjustedHeight = height;
149422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        Layout layout = getLayout();
149522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        if (layout != null) {
149622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            adjustedHeight -= layout.getLineDescent(0);
149722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        }
149822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
14994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
15014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
15026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
15034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1504368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
15054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
15060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
15070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
15080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
15094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1510368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
15114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
15136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1514368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1515368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
15169024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1517368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
15189024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1519368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1520368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
15219024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
15226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
15236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
15246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
15256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
152677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1527368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
15284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
152977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
153077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
153177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
15324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
153377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
15340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
15354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
15378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
15388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
15398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
15408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1541a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    // Visible for testing.
1542a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira    /*package*/ void removeMoreChip() {
15434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
15444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
15454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
15464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
15474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
15484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
15494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
1550c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
155164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
155264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
1553c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                if (recipients == null || recipients.length == 0) {
1554c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                    return;
1555c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira                }
155664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
15574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
15584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
15596f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
15600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
15610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
15626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
15636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
156464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
156564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
156664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
156764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
156864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
156964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1570bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1571bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1572bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1573bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1574bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
15754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
15764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
15774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
15784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
15794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
15804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1581c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1582b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1583b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1584b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1586b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1587b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1589b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1590b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1591b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1593b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
15941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
15951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
15961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
15971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
15981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
15991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
16001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
16012f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
16021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1603b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1604b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1606b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1607b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1608b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1609b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1610b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1611b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1612b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1613fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1614b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
161583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1616b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
161783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
16188b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
161983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1620b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
16211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
16221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1623c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
16241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
16256ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1627b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
16281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
16291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
16301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
16311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
16321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
16331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
16341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
16351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
16361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
16371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1638b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
16391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
16401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
16411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
16421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
16431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
16441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
16451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
16461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
16471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
16481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
16491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
16506ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
16511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1652fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1653b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1654fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
16561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
16571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
16581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
16591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
16601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
16611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1662e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
16651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
16661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
16671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
16691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
16711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
16721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
16731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
16741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
16761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
16771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
16781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
16791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1680b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1681b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1682b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1684b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1685b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1686b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1687b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1688b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1689c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1690b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
16915753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            Log.w(TAG,
16925753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    "The chip doesn't exist or may be a chip a user was editing");
16935753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            setSelection(editable.length());
16945753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            commitDefault();
1695b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1696c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1697b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
16988b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
16998b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
17008b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
17018b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17028b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
17038b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
17048b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1705c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1706b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1707b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1708b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1709b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1710c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1711b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1712c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1713b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1714b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1715b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1716b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1717b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1718b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1719ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira
1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1722c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1724b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1725c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1726c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1727b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1728b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1729b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1730b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1731b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1732b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1733b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1734b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1735b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1736b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1737b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1738b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1739b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1740b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
17413656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1742b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1743b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1744b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1745ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    // Visible for testing.
1746ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1751b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        int toDelete = spanEnd;
1752b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1756c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1757b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        // Always remove trailing spaces when removing a chip.
1758b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1759b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira            toDelete++;
1760b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        }
1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1762b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira        text.delete(spanStart, toDelete);
1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1764b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1765c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1766b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1767c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1768b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1769b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1771b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1772b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1773b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1774b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1775b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1776c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1778b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1785b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1786ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira            if (!TextUtils.isEmpty(chipText)) {
1787b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                // There may be a space to replace with this chip's new
1788b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                // associated
1789b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                // space. Check for it
1790b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                int toReplace = end;
1791b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                while (toReplace >= 0 && toReplace < editable.length()
1792b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                        && editable.charAt(toReplace) == ' ') {
1793b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                    toReplace++;
1794b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                }
1795b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira                editable.replace(start, toReplace, chipText);
1796b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1797c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1798b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1799b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1800b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1801c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1802b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1803c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1804b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1805b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1806b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1807b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1808b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1809b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1810b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1811b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1814b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1817b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1818c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1819368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1820368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1821368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1822368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1823311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1824311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1825311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1826311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1827311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1828311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1829e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1830e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1831e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
18321e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
18331e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
18341e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
18351e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
18361e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
18371e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
18381e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
18391e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
18401e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
18411e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
18421e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
18431e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
18441e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
18451e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
18461e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
18471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
18481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1849368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1850e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1851e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
18525753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            // If the user is editing a chip, don't clear it.
18535753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira            if (mSelectedChip != null
18545753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
1855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1858e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1861054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1863054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
186476ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1865054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1866054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1867054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1868054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1869054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1870054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1875e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1877e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1878e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1879e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
18803b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
1881e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1883e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1884e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1885e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
18891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1890e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
189422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira            // Do nothing.
1895e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1896e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
189777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
189877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
189977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
190077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
190177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
190277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
190377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
190477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
190577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
190677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
190777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
190877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
190977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
191077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
191177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
191277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
191377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
191477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
191577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
191677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
19176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
191877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
191977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
192077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
192177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
192277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
192377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
192477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
192577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
19268659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                addresses[i] = createDisplayText(originalRecipients.get(i).getEntry());
192777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
192877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
192977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
193077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
193177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
193277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
19331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
193477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
193577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
19361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
19371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
193877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
193977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
194077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
194177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
194277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
194377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
194477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
194577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
194677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
194777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
194877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
194977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
195077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
195177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
195277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
195377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
195477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
195577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
195677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1957b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1958b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1959b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
19606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
19616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
1962b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
19636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
196477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
196577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
196677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
196777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
196877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
196977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
197077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
197177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
197277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
197377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
197477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
197577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
197677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
197777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
197877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
197977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
198077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
198177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
198277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
198377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
198477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
198577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
198677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
198777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
198877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
19898659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira                addresses[i] = createDisplayText(originalRecipients.get(i).getEntry());
199077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
199177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
199277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
199377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
19941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
199577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
199677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
19971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
19981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
199977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
200077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
200177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
200277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
200377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
200477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
200577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
200677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
200777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
200877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
200977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
201077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
201177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
2012b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
20136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
20146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
20156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
20166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
20176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
20186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
20196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
20206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
20216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
20226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
20236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
2024b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2025b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
2026b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2027b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2028b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2029b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2030b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2031b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2032b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2033b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2034b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2035b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2036b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
2037b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
2038b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
2039b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2040b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
2041b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
2042b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
2043b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
2044b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
2045b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
2046b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
2047b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
2048b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2049b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2050b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
2051b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
2052b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
2053b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
2054b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
2055b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
2056b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
2057b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
2058b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
2059b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2060b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2061b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2062b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
2063b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2064b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2065b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2066b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2067b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2068b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
2069b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2070b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2071b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2072b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2073b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
2074b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
2075b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
2076b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2077b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2078b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2079b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
2080b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
2081b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
2082b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
2083b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
2084b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
2085b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
2086b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
2087b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
2088b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
2089b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
2090b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
209177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
2092