RecipientEditTextView.java revision 6f9f2858818228eaa1f2a2c562f4d2da6a4216b3
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;
61572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener;
62416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent;
63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView;
64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
65cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable;
66b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter;
67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow;
68156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView;
692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView;
70416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
71c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
72b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
73a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
75b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
7877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
79c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
86b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
87b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
88b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener {
89c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
90c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
95c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
96c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
113c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
115054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
116a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
117e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
118e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
119e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
123045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
124045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
125156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
126156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
127156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
131c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
132c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
13377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
13605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
13705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
138c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
139e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
140dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
141b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
142b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
14677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
149bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
150bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
151b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
161a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
162a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
166416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
167e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
168e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
171416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
173e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
185a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
186a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
1942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
19677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
19777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
19877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
2001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
202b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
205368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
209368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
215c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
217156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
218156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
219156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
220156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
221b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
222156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
223156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2286ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
229b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
230ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
231cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
232ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
233ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
234ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
235ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
236ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
237ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
24483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
24583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
24683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
24783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2514fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
252d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
253d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
254d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
255d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
256c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
257c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
258c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
259c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
260c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
261c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
262c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
263c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
264001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
265001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
266001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
267001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
268c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
269c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
2704f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
27105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
272c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
273c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
275a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
276c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
277c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
278a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
279a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
280a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
281a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
2826ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
283c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
284c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
285d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
286d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
287d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
2884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
2896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            dismissDropDown();
2904fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
2914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
292416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
2934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
2942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
2964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
2974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mSelectedChip != null) {
2984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
2994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
300001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
301001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
302001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
303a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
304001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
305001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
308001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(getText(), start);
311001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    if (whatEnd != getSelectionEnd()) {
314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
315001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
318001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
319090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
320001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3220fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
33077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
33177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
33277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
33377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
33477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
33577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
339e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
340c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
341c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
342c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
343c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
344c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
346c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
347e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
351e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
3561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
3591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
361c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
3631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
3661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
36877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
371e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    - Math.abs(height - mChipFontSize)/2, paint);
3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
3731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
3761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
3771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
380c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
381045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
382045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
383045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
384045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
385045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
386a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
387045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
388045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
389045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
390e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
394e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
3964221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
3974221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
3984221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
3994221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4011a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4021a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
406c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
409c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
410045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
411045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
412045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
413045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4159024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4179024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
41890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
41990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
42090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
42190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
42290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
42390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
42490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
42590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
42690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4279024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4289024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4299024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4309024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4319024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4329024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4349024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
4359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
4369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
437379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira                RectF dst = new RectF(width - iconWidth, 0, width, height);
4389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
4399024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4429024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
4441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
446379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
44790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    height - Math.abs(height - mChipFontSize) / 2, paint);
448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
453c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
461c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
46477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
4651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
468e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
471045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
473c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
475c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
4761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
47777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
48077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
481c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
4822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
485045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
486045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
4875519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
4885519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
4898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
490c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
4915519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
492416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
493b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        return -((actualLine * ((int)mChipHeight) + getPaddingBottom()) + getPaddingTop());
494f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
495f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
4968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
4978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
4988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
4998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
501c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5032d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5064f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5074f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5084f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5104f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5131426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5151426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
517b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
518b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
52043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
521045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
522d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
523b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
524b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
525b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
526b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
527b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
528b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
529b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
530b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
531b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
532b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
533b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
534b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
535b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
536b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
537bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
538bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
539bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
540bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
541bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
542bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
543bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
544bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
545bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
546bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
547bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
548c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
549c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
550c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
5517bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (width != 0 && height != 0 && mPendingChipsCount > 0) {
5527bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            postHandlePendingChips();
5537bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
55477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
555416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
556416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
557416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
558416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
559416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
560416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
561416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
562416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
563416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
564416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
565c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
566c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
567a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
568a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
569a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
570a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
571a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
5720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void handlePendingChips() {
573a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount <= 0) {
574a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            return;
575a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
5767bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
5777bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
5787bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
5797bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
5807bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
5817bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
582a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
583a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount);
584a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
585a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
586a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
588a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
589a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
590a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
591a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
592a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
593a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
594a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
595a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
596a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
597a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
5980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
6000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
601a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            if (mTemporaryRecipients != null
603a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
605a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
61277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
617a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // to
618a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // showing addresses for all of them.
619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
62077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
62177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
6240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6250fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
6290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
6310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
6320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
6330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
6340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
6350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
6360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
6370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
6390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
6400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
6420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
6430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
6601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
6611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
6621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
6631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
6641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
665b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
6661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
671d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
672d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            String destText = entry.getDestination();
673d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            destText = (String) mTokenizer.terminateToken(destText);
674d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
675d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int textLength = destText.length() - 1;
676d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
677d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
678d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
679d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
680d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
681d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
682d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
683d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
684d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
685d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
6860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
687d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
688d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
689d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
6906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
691d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
692d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
69377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
6940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
697d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
698d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
699d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
7000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
7011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
7026ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
703454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
704454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
7051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
7061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
7071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
7086ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
7096ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
7106ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
7116ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
7126ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
7131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
7141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
7156ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
7166ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
7176ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
7186ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
7196ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            token = mValidator.fixText(token).toString();
720490556a764a879cd0eaff358e90705cc1335c92eErik            if (!TextUtils.isEmpty(token)) {
721490556a764a879cd0eaff358e90705cc1335c92eErik                // protect against the case of a validator with a null domain,
722490556a764a879cd0eaff358e90705cc1335c92eErik                // which doesn't add a domain to the token
723490556a764a879cd0eaff358e90705cc1335c92eErik                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token);
724490556a764a879cd0eaff358e90705cc1335c92eErik                if (tokenized.length > 0) {
725490556a764a879cd0eaff358e90705cc1335c92eErik                    token = tokenized[0].getAddress();
726490556a764a879cd0eaff358e90705cc1335c92eErik                }
727d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7286ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
729454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
7301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return RecipientEntry.constructFakeEntry(token);
7311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
7321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
7336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
7346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
7356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
7366ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
7371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
7381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
7391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
7401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
745c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
746c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
747c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
748c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
749c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
750c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
7528684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
7538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
7548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
7558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
7568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
7578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
7598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
7608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
7618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
762c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
763c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
764c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
765c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
766c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
7698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
770c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
77195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
77295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
77395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
77495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
77595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
77695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
77795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
7788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
7808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
7818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
7828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
7838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
7848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
7858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
78695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
787c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
788c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
789c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
790c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
791c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
792d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
793c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
794c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
795e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
796e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
797e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
798e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
799e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
800e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
801c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
80295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
803e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
804e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
805e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
806e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
807e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
808e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
809e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
810e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
811e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
812e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
813e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
817c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
818e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
819e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
820e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
821e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
822e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
823e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
824e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
825e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
826e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
827045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
828045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
829045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
830045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
831045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
832045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
833045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
8348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
8354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
836045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
8374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
838dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
839e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
840e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
841e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
842e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
843e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
844e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
845e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
8464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
847e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
848e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
849e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
850e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
851e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
852e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
853e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
854e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
8584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
859054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
8614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
863001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
866e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
868e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
8731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
874d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
875d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
876d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
877d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    editable.replace(start, end, chipText);
878d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
879d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
8804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
881d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
882d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
8834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
884d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
885d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
8871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
8881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
8891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
8901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
8911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
8921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
8931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
89405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
8951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
896e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
897e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
899e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
905e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
906e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        CharSequence chipText = createChip(entry, false);
907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        editable.replace(start, getSelectionEnd(), chipText);
908054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
90905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
91005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
9118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
9138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
9148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
916c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
917b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
918b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
919b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
920b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
921b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
922c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
923c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
924c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
925c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
926c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
927c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
928c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
9292d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
9302d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
931c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
932ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
933c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
935b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
936b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
937b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
938b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
939b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
940b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
941b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
942b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
944c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
945c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
947c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
950c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
951c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
952c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
953c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
954c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
955c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
968b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
97136d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
9768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
9778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
9788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
9798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
9808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
9818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
982c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
983c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
984d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
985d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
986d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
987d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
988d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
990d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
991c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
992b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (action == MotionEvent.ACTION_DOWN && mSelectedChip == null) {
993b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
994b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
995b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mCopyDialog == null && action == MotionEvent.ACTION_UP) {
996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
997c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
998c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
999c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1000c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1004b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
10068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // Selection may have moved due to the tap event,
10078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // but make sure we correctly reset selection to the
10088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // end so that any unfinished chips are committed.
10098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
10108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1011b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1013b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1016c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1017416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
1018c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1022c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1023c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1024c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1025c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1026416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1027416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1028c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1029416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1030416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1031416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1032b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1033b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1034b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1035c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1036b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1037b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1038b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
103977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1040416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1041b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1042368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1043b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1044b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1045b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1046b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1047b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1051b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1052b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1053b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1054b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1055b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1056b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1057b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1058b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1059b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1060b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
10611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
10621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
10631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
10641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
10651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1066a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1067b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1068b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1069b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1070b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1071b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = position;
1073b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1074b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1075b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1077c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1079c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1080c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1081c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
10944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
10954fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
10984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1099c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
11074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
11084fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
11094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
11104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
11114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
11124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1114c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1119b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1120b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1127fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1128c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        String displayText = entry.getDestination();
11294f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira        displayText = (String) mTokenizer.terminateToken(displayText);
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1131b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        int textLength = displayText.length()-1;
1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
11366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
11376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
11396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11488684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11498684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
11508684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
11518684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
11580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
11590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
11601e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
11611e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
11621e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
11704221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
11714221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
11724221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
11734221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
11770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
11780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
11790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
11810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
11820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
11830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
11840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1185c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1186c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
11870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
11880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
11890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
11900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
11910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
11930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
11940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1195c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
11987a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
11997a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12007a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12017a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
12027a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1206c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
120783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
120883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
120983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
121083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
12116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private RecipientChip[] getSortedRecipients() {
12126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
12136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                .asList(getRecipients()));
12146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
12156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
12166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
12176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
12186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
12196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
12206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
12216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
12226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
12236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
12246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
12256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
12266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
12276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
12286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
12296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
12306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
12316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
12326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1233c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1234c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1235c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
123683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
12377a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12387a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12397a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
12407a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1243c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1244c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
12484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
12534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
12574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
12628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
12654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1269045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1270045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1271045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
12728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createMoreChip() {
1274bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1275bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1276bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1277bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
12786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
12796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
12806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
12816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
12826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
128383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
12840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
12850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
12864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
12876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
128883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
12894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1290c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1291c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1292c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1293c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1294c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1295c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
12964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
12974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
12984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
12994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
1300c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                morePaint);
13014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
13034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
13046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
13054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1306368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
13074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
13080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
13090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
13100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
13114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1312368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
13134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
13144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
13156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1316368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1317368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
13189024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1319368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
13209024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1321368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1322368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
13239024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
13246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
13256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
13266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
13276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
132877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1329368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
13304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
133177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
133277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
133377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
13344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
133577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
13360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
13374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13408684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
13418684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
13428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
13444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
13454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
13464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
13474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
13484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
13494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
13504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
13514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
13524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
13536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
13540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
13550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
13566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
13576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
13586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    chipStart = editable.toString().indexOf(token);
13596f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // -1 for the space!
13606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    chipEnd = Math.min(editable.length(), chipStart + token.length());
1361bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1362bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1363bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1364bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1365bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
13664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
13674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
13684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
13694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
13704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1372c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1373b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1374b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1375b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1376b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1377b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1378b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1379b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1380b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1381b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1382b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1384b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
13851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
13861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
13871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
13881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
13891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
13901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
13911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
13921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return null;
13931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1394b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1395b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1396b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1397b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1398b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1399b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1400b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1401b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1402b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1403b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1404fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1405b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
140683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1407b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
140883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
14098b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
141083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1411b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
14121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1414c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
14151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
14166ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1417b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1418b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
14191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
14201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
14211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
14221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
14231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
14241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
14251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
14261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
14281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1429b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
14301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
14321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
14331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
14341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
14371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
14391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
14416ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
14421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1443fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1444b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1445fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
14481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
14491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
14501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
14511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
14521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
14531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
14541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
14551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
14561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
14571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
14581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
14591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
14601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
14611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
14621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
14641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
14651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
14661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
14671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
14681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
14691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1470b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1471b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1472b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1473b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1474b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1475b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1476b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1477b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1478b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1479c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1480b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1481b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip being unselected no longer exists but should.");
1482b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1483c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1484b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14858b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
14868b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
14878b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
14888b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14898b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
14908b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14918b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1492c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1493b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1494b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1495b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1496b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1498b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1501b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1502b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1503b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1504b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1505b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1506b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1507b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1514b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1516b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1517b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1518b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1522b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1524b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
15283656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1529b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1530b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1532b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void removeChip(RecipientChip chip) {
1533b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int toDelete = spanEnd;
1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1540b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1542c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Always remove trailing spaces when removing a chip.
15448b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1545b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            toDelete++;
1546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1547b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        text.delete(spanStart, toDelete);
1549b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1556b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1557b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1558b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1559b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1561b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1562c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1564b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1566b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1567b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1568b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1569b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1572b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // There may be a space to replace with this chip's new associated
1573b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // space. Check for it.
1574b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            int toReplace = end;
1575b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            while (toReplace >= 0 && toReplace < editable.length()
1576b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                    && editable.charAt(toReplace) == ' ') {
1577b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                toReplace++;
1578b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1579b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            editable.replace(start, toReplace, chipText);
1580c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1581b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1582b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1583b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1587b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1589b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1590b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1591b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1592b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1593b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1595b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1598c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1599c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1601c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1602368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1603368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1604368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1605368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1606311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1607311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1608311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1609311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1610311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1611311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1612e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1613e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1614e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
16151e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
16161e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
16171e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
16181e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
16191e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
16201e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
16211e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
16221e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
16231e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
16241e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16251e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
16261e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
16271e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16281e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
16291e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
16301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
16311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1632368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1633e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1634e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1635e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mSelectedChip != null) {
1636e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1637e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1638e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1639e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1640e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1641e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1642054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1643e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1644054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
164576ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1646054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1647054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1648054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1649054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1650054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1651054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1652e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1653e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1654e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1655e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1656e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1657e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1658e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1659e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1660e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
1661e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    if (mValidator != null && mValidator.isValid(sub)) {
1662e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1663e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1664e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1665e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1666e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1667e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1668e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1669e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1671e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1672e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1673e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1674e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1675e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1676e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
167777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
167877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
167977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
168077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String displayText = entry.getDestination();
16816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (displayText.indexOf(",") == -1) {
16826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                displayText = (String) mTokenizer.terminateToken(displayText);
16836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
168477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
168577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
168677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
168777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
168877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
168977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
169077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
169177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
169277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
169377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
169477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
169577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
169677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
169777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
169877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
169977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
170077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
17016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
170277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
170377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
170477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
170577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
170677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
170777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
170877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
170977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
171077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
171377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
171477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
171577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
171677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
17171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
171877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
171977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
17211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
172277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
172577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
172677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
173277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
173477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
173577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
173677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
173777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
173877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1741b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1742b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1743b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
17446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
17456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
1746b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
174877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
174977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
175077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
175177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
175277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
175377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
175477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
175677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
175777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
175877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
175977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
176077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
176177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
176277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
177277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
177377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
177477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
177577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
177677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
177777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
17781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
177977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
178077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
17821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
178377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
178677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
178777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
178877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
178977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
179077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
179177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
179277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
179377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
179477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
179577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
1796b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
17976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
17986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
17996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
18006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
18016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
18026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
18036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
18046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
18056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
18066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
18076f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1808b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1809b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
1810b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1811b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1812b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1813b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1814b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1815b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1816b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1817b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1818b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1819b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1820b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
1821b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
1822b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
1823b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1824b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
1825b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
1826b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
1827b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
1828b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
1830b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
1831b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1832b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1833b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1834b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
1835b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
1836b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = new Dialog(getContext());
1837b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
1838b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
1839b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
1840b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
1841b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
1842b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
1843b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
1844b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1845b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1846b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1847b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1848b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1849b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1850b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1851b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1852b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1853b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
1854b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1855b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1856b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1857b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1858b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
1859b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1860b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1861b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1862b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1863b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1864b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
1865b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
1866b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = null;
1867b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1868b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1869b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1870b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
1871b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
1872b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
1873b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
1874b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
1875b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
1876b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
187777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
1878