RecipientEditTextView.java revision 6caf49d02e7b2a719fc9fdf57e3f8e96dfdf082a
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;
362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable;
37572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType;
38c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout;
39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable;
40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString;
4177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder;
42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned;
43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint;
44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils;
45c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher;
46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener;
47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan;
480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token;
490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet;
51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log;
524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode;
53a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback;
54b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector;
55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent;
56c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater;
574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu;
584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem;
59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent;
60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View;
6197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereiraimport android.view.ViewGroup;
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;
71416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
72c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
73b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
74a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
76b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
7977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
87b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
88b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
89b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener {
90c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
96c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
114c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
116054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
117a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
118e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
119e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
124045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
125045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
126156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
127156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
132c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
133c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
13477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
13705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
139c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
140e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
141dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
142b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
14777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
150bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
151bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
161b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
162a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
163a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
167416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
168e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
169e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
171416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
186a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
1952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
19777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
19877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
19977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
200b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
2011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
2020436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
205b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
207368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
211368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
215b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
217c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
219156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
220156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
221156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
222156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
227156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
228156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
231b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
232ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
233cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
234ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
235ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
236ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
237ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
238ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
239ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
24683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
24783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
24883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
24983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2504fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2514fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
252b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
254d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
255d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
256d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
257d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
258c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
259c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
260c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
261c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
262c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
263c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
264c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
265c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
266001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
267001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
268001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
269001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
270c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
271c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
2724f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
27305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
275c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
276c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
277a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
278c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
279c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
280a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
281a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
282a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
283a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
2846ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
285c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
286c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
287d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
288d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
289b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
290d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
2914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
2924fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
2934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
294416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
2954fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
2962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
298b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
299b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
300b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
301b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
302b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3045a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3055a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
308001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
311a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
315001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
318001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(getText(), start);
319001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
320001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
321001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    if (whatEnd != getSelectionEnd()) {
322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
324001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
325001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
326001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
327090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
328001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
33877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
33977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
34077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
34177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
34277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
34377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
347e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
348c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
349c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
350c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
351c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
352c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
355e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
359e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
3631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
3661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
3681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
369c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
3731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
37677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
37797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
37897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
37997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
3801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
3811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
3821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
3831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
3851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
3861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
3871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
389045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
390045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
391045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
392045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
393045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
394a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
395045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
396045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
397045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
398e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
402e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
4044221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
4054221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
4064221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
4074221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4091a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4101a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
418045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
419045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
420045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
421045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4239024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4259024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
42690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
42790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
42890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
42990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
43090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
43190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
43290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
43390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
43490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4379024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4399024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4409024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4429024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
4439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
4449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
445379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira                RectF dst = new RectF(width - iconWidth, 0, width, height);
4469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
4479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4499024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
45397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
454379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
45597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
46297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
46397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
46497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        paint.getTextBounds((String)text, 0, text.length(), bounds);
46597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
46697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
46797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
46897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
4691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
4701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
476c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
4771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
4781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
47977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
4801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
4821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
483e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
486045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
4871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
489c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
490c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
4911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
49277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
493c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
494c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
49577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
4972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
500045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
501045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5025519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5035519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
505c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5065519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
507416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
50897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
50997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
510f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
511f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5224f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5234f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5244f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5264f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5291426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5311426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
532c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
533b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
534b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
535c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
53643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
537045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
538d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
539b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
540b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
541b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
542b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
543b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
544b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
545b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
546b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
547b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
548b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
549b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
550b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
551b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
552b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
553bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
554bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
555bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
556bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
557bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
558bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
559bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
560bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
561bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
562bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
563bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
565c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
566c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
5677bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (width != 0 && height != 0 && mPendingChipsCount > 0) {
5687bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            postHandlePendingChips();
5697bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
57077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
571416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
572416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
573416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
574416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
575416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
576416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
577416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
578416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
579416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
580416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
581c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
582c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
583a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
584a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
585a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
586a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
5880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void handlePendingChips() {
589a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount <= 0) {
590a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            return;
591a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
5927bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
5937bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
5947bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
5957bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
5967bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
5977bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
598a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount);
600a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
601a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
603a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
605a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
6140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
6160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
617a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
618a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            if (mTemporaryRecipients != null
619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
620a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
625a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
626a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
62877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
629a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
630a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
631a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
632a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
633a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // to
634a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // showing addresses for all of them.
635a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
63677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
63777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
638a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
639a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
6400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
6600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
6730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
6740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
6761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
6771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
6781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
6791e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
6801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
681b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
6821e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
6830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
6840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
6850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
687d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
688d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            String destText = entry.getDestination();
689d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            destText = (String) mTokenizer.terminateToken(destText);
690d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
691d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int textLength = destText.length() - 1;
692d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
693d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
694d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
695d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
696d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
697d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
698d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
699d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
700d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
701d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
703d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
704d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
705d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
7066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
707d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
708d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
70977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
7100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
713d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
714d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
715d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
7160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
7171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
7186ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
719454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
720454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
7211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
7221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
7231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
7246ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
7256ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
7266ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
7276ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
7286ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
7291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
7301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
7316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
7326ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
7336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
7346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
7356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            token = mValidator.fixText(token).toString();
736490556a764a879cd0eaff358e90705cc1335c92eErik            if (!TextUtils.isEmpty(token)) {
737490556a764a879cd0eaff358e90705cc1335c92eErik                // protect against the case of a validator with a null domain,
738490556a764a879cd0eaff358e90705cc1335c92eErik                // which doesn't add a domain to the token
739490556a764a879cd0eaff358e90705cc1335c92eErik                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token);
740490556a764a879cd0eaff358e90705cc1335c92eErik                if (tokenized.length > 0) {
741490556a764a879cd0eaff358e90705cc1335c92eErik                    token = tokenized[0].getAddress();
742490556a764a879cd0eaff358e90705cc1335c92eErik                }
743d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7446ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
745454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
7461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return RecipientEntry.constructFakeEntry(token);
7471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
7481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
7496ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
7506ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
7516ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
7526ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
7531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
7541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
7551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
7561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
761c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
763c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
764c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
765c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
766c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
7688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
7698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
7708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
7718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
7728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
7738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
7758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
7768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
7778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
779c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
780c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
781c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
782c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
7858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
786c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
78795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
78895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
78995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
79095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
79195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
79295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
79395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
7948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
7968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
7978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
7988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
7998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
8008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
8018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
80295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
803c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
804c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
808d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
809c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
811e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
812e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
813e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
814e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
815e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
816e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
817c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
81895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
819e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
820e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
821e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
822e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
823e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
824e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
825e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
826e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
827e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
828e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
829e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
830c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
832c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
833c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
834e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
835e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
836e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
837e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
838e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
839e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
840e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
841e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
842e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
843045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
844045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
845045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
846045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
847045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
848045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
849045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
8508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
8514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
852045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
8534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
854dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
858e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
8624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
863e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
866e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
868e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
8744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
875054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
8774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
878e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
879001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
880e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
881e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
883e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
884e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
885e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
8891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
890d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
891d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
892d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
893d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    editable.replace(start, end, chipText);
894d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
895d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
8964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
897d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
898d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
8994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
900d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
901d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
9031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
9041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
9051e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
9061e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
9071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
9081e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
9091e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
91005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
9111e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
921e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
922e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        CharSequence chipText = createChip(entry, false);
923e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        editable.replace(start, getSelectionEnd(), chipText);
924054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
92505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
92605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
9278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
9298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
9308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
931c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
932c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
933b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
934b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
935b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
936b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
937b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
940c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
941c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
942c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
944c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
9452d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
9462d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
947c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
948ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
950c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
951b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
952b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
953b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
954b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
955b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
956b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
957b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
958b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
976c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
980c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
982c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
983c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
984b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
985c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
986c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
98736d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
988c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
9928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
9938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
9948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
9958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
9968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
9978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
998c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
999c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1000d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1001d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1002d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1003d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1004d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1006d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1007c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
10086caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1009b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1010b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
10110436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1013c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1016c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1018c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1020b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
10228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // Selection may have moved due to the tap event,
10238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // but make sure we correctly reset selection to the
10248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // end so that any unfinished chips are committed.
10258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
10268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1027b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1029b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1031c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1032c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1033416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1036c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1037c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1038c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1039c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1040c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1041c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1042416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1043416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1044c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1045416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1046416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1047416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1051c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1052b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1053b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1054b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
105577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1056416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1057b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1058368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1059e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1060e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1061b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1062b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1064b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1065b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1066b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1067b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1068b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1069b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1070b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1071b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1073b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1074b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1075b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1076b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1077b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1078b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
10791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
10801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
10811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
10821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
10831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1084a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1085b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1086b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1087b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1088b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1089b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1090e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1091b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
11114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
11124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
11154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1116c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
11244fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
11254fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
11264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
11274fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
11284fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
11294fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1131c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1135b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1136b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1137b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1144fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1145c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        String displayText = entry.getDestination();
11464f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira        displayText = (String) mTokenizer.terminateToken(displayText);
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1148b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        int textLength = displayText.length()-1;
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
11536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
11546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
11566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
11678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
11688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
11750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
11760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
11771e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
11781e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
11791e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1181c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1185c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1186c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
11874221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
11884221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
11894221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
11904221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
11940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
11950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
11960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
11980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
11990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
12000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
12010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1202c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1203c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
12040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
12050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
12060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
12070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
12080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
12090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
12100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
12110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
12157a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
12167a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12177a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12187a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
12197a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
122483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
122583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
122683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
122783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
12286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private RecipientChip[] getSortedRecipients() {
12296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
12306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                .asList(getRecipients()));
12316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
12326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
12336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
12346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
12356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
12366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
12376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
12386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
12396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
12406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
12416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
12426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
12436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
12446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
12456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
12466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
12476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
12486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
12496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1251c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
125383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
12547a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12557a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12567a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
12577a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1260c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1261c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
12654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
12704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
12744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
12798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
12824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1286045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1287045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1288045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
12898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createMoreChip() {
1291bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1292bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1293bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1294bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
12956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
12966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
12976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
12986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
12996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
130083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
13010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
13020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
13034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
13046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
130583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
13064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1307c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1308c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1309c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1310c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1311c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1312c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
13134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
13144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
13154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
13164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
1317c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                morePaint);
13184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
13204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
13216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
13224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1323368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
13244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
13250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
13260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
13270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
13284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1329368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
13304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
13314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
13326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1333368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1334368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
13359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1336368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
13379024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1338368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1339368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
13409024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
13416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
13426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
13436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
13446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
134577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1346368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
13474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
134877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
134977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
135077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
13514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
135277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
13530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
13544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
13588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
13598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
13614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
13624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
13634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
13644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
13654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
13664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
13674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
13684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
13694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
13706f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
13710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
13720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
13736f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
13746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
13756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    chipStart = editable.toString().indexOf(token);
13766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // -1 for the space!
13776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    chipEnd = Math.min(editable.length(), chipStart + token.length());
1378bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1379bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1380bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1381bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1382bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
13834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
13844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
13854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
13864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
13874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1390b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1391b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1392b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1393b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1394b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1395b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1396b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1397b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1398b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1399b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1401b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
14021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
14031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
14041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
14051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
14061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
14071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
14081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
14092f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
14101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1411b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1412b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1413b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1414b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1415b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1416b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1417b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1418b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1419b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1420b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1421fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1422b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
142383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1424b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
142583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
14268b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
142783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1428b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
14291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1431c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
14321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
14336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1434b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1435b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
14361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
14371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
14381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
14391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
14401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
14411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
14421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
14431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
14451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1446b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
14491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
14501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
14511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
14541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
14561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
14586ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
14591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1460fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1461b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1462fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1463c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
14651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
14661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
14671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
14681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
14691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
14701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
14711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
14721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
14731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
14741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
14751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
14761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
14771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
14781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
14791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
14811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
14821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
14831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
14841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
14851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
14861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1487b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1488b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1489b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1490b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1491b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1492b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1493b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1494b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1495b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1496c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1497b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
14982f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            Log.e(TAG, "The chip being unselected no longer exists.");
1499b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1500c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1501b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
15028b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
15038b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
15048b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
15058b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
15068b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
15078b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
15088b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1512b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1513b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1518b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1522b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1524b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1526c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1528b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1529c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1532b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1533b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1540b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1542b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
15453656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1547b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1549b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void removeChip(RecipientChip chip) {
1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1551b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1553b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int toDelete = spanEnd;
1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1556b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1557b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1558b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Always remove trailing spaces when removing a chip.
15618b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1562b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            toDelete++;
1563c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1564b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        text.delete(spanStart, toDelete);
1566b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1567b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1569b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1570c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1576b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1577b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1578b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1580b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1581b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1582b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1583b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1584b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1586b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1587b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1589b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // There may be a space to replace with this chip's new associated
1590b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // space. Check for it.
1591b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            int toReplace = end;
1592b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            while (toReplace >= 0 && toReplace < editable.length()
1593b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                    && editable.charAt(toReplace) == ' ') {
1594b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                toReplace++;
1595b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1596b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            editable.replace(start, toReplace, chipText);
1597c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1601c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1603c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1604b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1606b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1607b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1608b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1609b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1610b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1611b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1612b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1613b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1614b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1615c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1616c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1617b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1618c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1619368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1620368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1621368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1622368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1623311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1624311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1625311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1626311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1627311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1628311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1629e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1630e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1631e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
16321e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
16331e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
16341e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
16351e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
16361e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
16371e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
16381e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
16391e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
16401e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
16411e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16421e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
16431e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
16441e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16451e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
16461e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
16471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
16481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1649368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1650e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1651e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1652e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mSelectedChip != null) {
1653e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1654e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1655e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1656e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1657e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1658e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1659054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1660e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1661054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
166276ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1663054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1664054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1665054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1666054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1667054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1668054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1669e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1670e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1671e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1672e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1673e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1674e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1675e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1676e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1677e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
1678e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    if (mValidator != null && mValidator.isValid(sub)) {
1679e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1680e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1681e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1682e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1683e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1684e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1685e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1686e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
16871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1688e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1689e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1690e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1691e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1692e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1693e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
169477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
169577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
169677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
169777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String displayText = entry.getDestination();
16986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (displayText.indexOf(",") == -1) {
16996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                displayText = (String) mTokenizer.terminateToken(displayText);
17006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
170177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
170277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
170377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
170477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
170577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
170677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
170777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
170877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
170977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
171077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
171377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
171477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
171577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
171677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
171777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
17186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
171977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
172077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
172177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
172277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
172577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
172677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
173277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
173577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
173677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
174177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
174277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
174377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
174477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
174577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
174677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
174777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
174877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
174977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
175077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
175177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
175277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
175377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
175477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
175677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
175777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1758b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1759b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1760b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
17616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
17626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
1763b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17646f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
177277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
177377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
177477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
177577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
177677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
177777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
177877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
177977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
178077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
178177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
178277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
178377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
178677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
178777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
178877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
178977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
179077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
179177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
179277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
179377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
179477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
17951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
179677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
179777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
17991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
180077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
180177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
180277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
180377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
180477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
180577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
180677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
180777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
180877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
180977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
181077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
181177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
181277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
1813b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
18146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
18156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
18166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
18176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
18186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
18196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
18206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
18216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
18226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
18236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
18246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1825b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1826b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
1827b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1828b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1830b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1831b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1832b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1833b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1834b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1835b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1836b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1837b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
1838b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
1839b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
1840b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1841b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
1842b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
1843b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
1844b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
1845b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
1846b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
1847b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
1848b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1849b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1850b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1851b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
1852b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
1853b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
1854b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
1855b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
1856b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
1857b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
1858b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
1859b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
1860b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1861b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1862b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1863b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1864b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1865b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1866b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1867b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1868b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1869b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
1870b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1871b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1872b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1873b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1874b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
1875b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1876b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1877b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1878b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1879b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1880b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
1881b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
1882b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1883b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1884b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1885b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
1886b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
1887b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
1888b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
1889b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
1890b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
1891b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
189277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
1893