RecipientEditTextView.java revision b67763f7bccadc3a34037d9d47c7f3107f639195
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) {
287b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
288d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
2894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
2904fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
2914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
292416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
2934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
2942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
2952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
296b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
297b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
298b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
299b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
300b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mSelectedChip != null) {
3034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
305001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
308a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
311001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
315001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(getText(), start);
316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
318001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    if (whatEnd != getSelectionEnd()) {
319001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
320001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
321001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
324090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
325001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
33577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
33677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
33777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
33877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
33977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
34077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
344e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
345c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
346c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
347c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
348c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
349c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
352e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
356e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
3591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
3631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
3681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
37377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height
376e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira                    - Math.abs(height - mChipFontSize)/2, paint);
3771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
3801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
3811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
3821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
3831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
385c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
386045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
387045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
388045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
389045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
390045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
391a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
392045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
393045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
394045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
395e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
399e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
4014221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
4024221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
4034221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
4044221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4061a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4071a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
415045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
416045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
417045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
418045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4209024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4229024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
42390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
42490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
42590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
42690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
42790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
42890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
42990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
43090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
43190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4329024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4349024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4379024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4399024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
4409024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
442379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira                RectF dst = new RectF(width - iconWidth, 0, width, height);
4439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
4449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Align the display text with where the user enters text.
451379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
45290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    height - Math.abs(height - mChipFontSize) / 2, paint);
453c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
4601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
466c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
4681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
46977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
4701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
473e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
476045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
4771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
480c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
4811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
48277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
483c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
48577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
4872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
4882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
4898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
490045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
491045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
4925519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
4935519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
4948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
495c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
4965519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
497416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
498b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        return -((actualLine * ((int)mChipHeight) + getPaddingBottom()) + getPaddingTop());
499f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
500f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5114f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5124f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5134f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5154f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5181426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5201426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
522b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
523b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
52543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
526045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
527d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
528b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
529b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
530b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
531b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
532b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
533b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
534b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
535b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
536b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
537b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
538b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
539b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
540b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
541b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
542bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
543bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
544bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
545bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
546bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
547bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
548bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
549bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
550bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
551bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
552bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
554c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
555c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
5567bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (width != 0 && height != 0 && mPendingChipsCount > 0) {
5577bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            postHandlePendingChips();
5587bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
55977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
560416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
561416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
562416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
563416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
564416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
565416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
566416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
567416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
568416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
569416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
570c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
571c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
572a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
573a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
574a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
575a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
576a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
5770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void handlePendingChips() {
578a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount <= 0) {
579a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            return;
580a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
5817bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
5827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
5837bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
5847bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
5857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
5867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
588a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount);
589a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
590a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
591a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
592a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
593a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
594a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
595a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
596a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
597a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
598a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
600a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
601a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
6030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
6050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            if (mTemporaryRecipients != null
608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
61777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
618a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
620a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // to
623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // showing addresses for all of them.
624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
62577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
62677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
6290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
6340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
6360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
6370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
6390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
6400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
6410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
6420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
6430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
6651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
6661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
6671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
6681e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
6691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
670b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
6711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
6730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
6740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
676d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
677d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            String destText = entry.getDestination();
678d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            destText = (String) mTokenizer.terminateToken(destText);
679d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
680d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int textLength = destText.length() - 1;
681d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
682d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
683d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
684d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
685d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
686d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
687d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
688d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
689d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
690d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
6910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
692d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
693d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
694d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
6956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
696d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
697d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
69877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
6990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
702d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
703d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
704d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
7050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
7061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
7076ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
708454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
709454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
7101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
7111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
7121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
7136ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
7146ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
7156ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
7166ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
7176ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
7181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
7191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
7206ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
7216ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
7226ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
7236ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
7246ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            token = mValidator.fixText(token).toString();
725490556a764a879cd0eaff358e90705cc1335c92eErik            if (!TextUtils.isEmpty(token)) {
726490556a764a879cd0eaff358e90705cc1335c92eErik                // protect against the case of a validator with a null domain,
727490556a764a879cd0eaff358e90705cc1335c92eErik                // which doesn't add a domain to the token
728490556a764a879cd0eaff358e90705cc1335c92eErik                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token);
729490556a764a879cd0eaff358e90705cc1335c92eErik                if (tokenized.length > 0) {
730490556a764a879cd0eaff358e90705cc1335c92eErik                    token = tokenized[0].getAddress();
731490556a764a879cd0eaff358e90705cc1335c92eErik                }
732d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
734454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
7351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return RecipientEntry.constructFakeEntry(token);
7361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
7371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
7386ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
7396ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
7406ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
7416ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
7421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
7431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
7441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
7451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
750c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
751c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
752c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
753c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
754c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
755c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
7578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
7588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
7598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
7608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
7618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
7628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
7648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
7658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
7668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
767c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
768c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
769c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
770c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
771c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
7748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
775c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
77695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
77795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
77895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
77995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
78095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
78195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
78295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
7838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
7858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
7868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
7878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
7888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
7898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
7908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
79195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
792c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
793c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
794c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
795c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
797d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
798c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
799c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
800e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
801e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
802e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
803e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
804e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
805e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
80795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
808e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
809e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
810e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
811e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
812e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
813e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
814e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
815e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
816e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
817e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
818e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
819c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
820c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
822c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
823e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
824e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
825e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
826e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
827e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
828e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
829e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
830e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
831e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
832045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
833045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
834045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
835045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
836045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
837045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
838045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
8398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
8404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
841045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
8424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
843dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
844e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
845e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
846e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
847e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
848e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
849e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
850e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
8514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
852e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
853e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
854e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
858e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
8634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
864054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
8664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
868001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
875e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
877e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
8781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
879d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
880d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
881d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
882d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    editable.replace(start, end, chipText);
883d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
884d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
8854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
886d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
887d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
8884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
889d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
890d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
8921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
8931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
8941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
8951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
8961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
8971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
8981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
89905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
9001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
905e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
906e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
909e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
911e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        CharSequence chipText = createChip(entry, false);
912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        editable.replace(start, getSelectionEnd(), chipText);
913054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
91405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
91505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
9168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
9188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
9198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
920c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
922b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
923b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
924b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
925b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
926b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
927c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
928c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
930c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
931c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
932c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
933c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
9342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
9352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
937ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
940b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
941b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
942b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
943b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
944b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
945b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
946b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
947b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
950c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
951c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
952c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
953c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
954c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
955c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
973b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
97636d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
9818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
9828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
9838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
9848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
9858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
9868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
987c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
988c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
989d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
990d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
991d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
992d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
993d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
994c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
995d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
997b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (action == MotionEvent.ACTION_DOWN && mSelectedChip == null) {
998b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
999b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1000b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mCopyDialog == null && action == MotionEvent.ACTION_UP) {
1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1004c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1006c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1007c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1008c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1009b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
10118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // Selection may have moved due to the tap event,
10128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // but make sure we correctly reset selection to the
10138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        // end so that any unfinished chips are committed.
10148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
10158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1016b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1018b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1022416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
1023c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1024c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1025c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1026c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1027c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1029c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1031416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1032416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1033c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1034416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1035416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1036416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1037b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1038b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1039b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1040c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1041b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1042b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1043b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
104477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1045416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1046b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1047368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1051b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1052b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1053b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1054b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1055b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1056b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1057b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1058b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1059b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1060b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1061b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1062b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1064b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1065b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
10661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
10671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
10681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
10691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
10701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1071a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1073b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1074b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1075b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1076b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1077b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = position;
1078b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1079b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1080b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1081c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
10984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
10994fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
11004fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
11034fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1104c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
11124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
11134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
11144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
11154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
11164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
11174fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1119c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1132fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1133c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        String displayText = entry.getDestination();
11344f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira        displayText = (String) mTokenizer.terminateToken(displayText);
1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1136b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        int textLength = displayText.length()-1;
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
11416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
11426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
11446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
11558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
11568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
11630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
11640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
11651e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
11661e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
11671e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
11754221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
11764221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
11774221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
11784221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
11820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
11830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
11840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
11860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
11870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
11880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
11890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1190c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1191c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
11920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
11930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
11940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
11950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
11960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
11970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
11980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
11990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1201c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
12037a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
12047a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12057a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12067a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
12077a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1208c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
121283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
121383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
121483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
121583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
12166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private RecipientChip[] getSortedRecipients() {
12176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
12186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                .asList(getRecipients()));
12196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
12206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
12216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
12226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
12236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
12246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
12256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
12266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
12276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
12286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
12296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
12306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
12316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
12326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
12336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
12346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
12356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
12366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
12376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
124183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
12427a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12437a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12447a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
12457a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1246c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
12534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
12584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
12624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
12678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
12704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1274045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1275045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1276045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
12778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createMoreChip() {
1279bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1280bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1281bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1282bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
12836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
12846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
12856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
12866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
12876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
128883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
12890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
12900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
12914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
12926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
129383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
12944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1295c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1296c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1297c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1298c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1299c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1300c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
13014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
13024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
13034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
13044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
1305c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                morePaint);
13064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
13084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
13096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
13104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1311368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
13124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
13130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
13140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
13150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
13164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1317368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
13184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
13194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
13206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1321368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1322368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
13239024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1324368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
13259024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1326368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1327368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
13289024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
13296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
13306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
13316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
13326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
133377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1334368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
13354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
133677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
133777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
133877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
13394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
134077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
13410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
13424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
13468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
13478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
13494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
13504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
13514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
13524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
13534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
13544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
13554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
13564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
13574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
13586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
13590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
13600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
13616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
13626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
13636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    chipStart = editable.toString().indexOf(token);
13646f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // -1 for the space!
13656f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    chipEnd = Math.min(editable.length(), chipStart + token.length());
1366bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1367bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1368bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1369bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1370bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
13714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
13724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
13734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
13744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
13754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1378b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1379b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1380b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1381b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1382b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1383b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1384b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1385b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1386b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1387b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1389b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
13901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
13911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
13921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
13931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
13941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
13951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
13961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
13971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return null;
13981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1399b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1400b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1401b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1402b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1403b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1404b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1405b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1406b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1407b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1408b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1409fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1410b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
141183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1412b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
141383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
14148b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
141583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1416b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
14171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1419c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
14201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
14216ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1422b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1423b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
14241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
14251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
14261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
14271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
14281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
14291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
14301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
14311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
14331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1434b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
14351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
14371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
14381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
14391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
14421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
14441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
14466ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1448fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1449b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1450fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
14531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
14541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
14551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
14561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
14571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
14581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
14591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
14601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
14611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
14621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
14631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
14641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
14651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
14661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
14671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
14691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
14701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
14711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
14721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
14731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
14741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1475b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1476b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1477b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1478b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1479b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1480b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1481b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1482b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1483b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1484c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1485b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1486b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip being unselected no longer exists but should.");
1487b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1488c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1489b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14908b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
14918b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
14928b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
14938b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14948b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
14958b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14968b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1498b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1499b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1500b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1501b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1503b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1506b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1507b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1509b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1512b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1513b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1516b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1522b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1524b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1528b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1529b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1530b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1532b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
15333656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void removeChip(RecipientChip chip) {
1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1540b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1542b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int toDelete = spanEnd;
1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1545b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Always remove trailing spaces when removing a chip.
15498b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            toDelete++;
1551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1553b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        text.delete(spanStart, toDelete);
1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1556c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1557b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1559b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1561b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1562b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1564b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1566b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1568b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1569b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1576b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1577b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // There may be a space to replace with this chip's new associated
1578b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // space. Check for it.
1579b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            int toReplace = end;
1580b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            while (toReplace >= 0 && toReplace < editable.length()
1581b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                    && editable.charAt(toReplace) == ' ') {
1582b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                toReplace++;
1583b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1584b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            editable.replace(start, toReplace, chipText);
1585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1586b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1587b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1590b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1592b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1593b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1595b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1601b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1603c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1604c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1606c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1607368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1608368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1609368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1610368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1611311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1612311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1613311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1614311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1615311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1616311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1617e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1618e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1619e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
16201e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
16211e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
16221e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
16231e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
16241e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
16251e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
16261e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
16271e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
16281e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
16291e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16301e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
16311e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
16321e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16331e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
16341e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
16351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
16361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1637368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1638e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1639e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1640e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mSelectedChip != null) {
1641e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1642e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1643e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1644e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1645e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1646e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1647054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1648e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1649054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
165076ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1651054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1652054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1653054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1654054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1655054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1656054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1657e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1658e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1659e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1660e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1661e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1662e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1663e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1664e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1665e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
1666e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    if (mValidator != null && mValidator.isValid(sub)) {
1667e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1668e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1669e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1670e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1671e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1672e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1673e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1674e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1676e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1677e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1678e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1679e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1680e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1681e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
168277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
168377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
168477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
168577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String displayText = entry.getDestination();
16866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (displayText.indexOf(",") == -1) {
16876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                displayText = (String) mTokenizer.terminateToken(displayText);
16886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
168977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
169077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
169177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
169277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
169377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
169477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
169577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
169677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
169777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
169877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
169977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
170077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
170177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
170277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
170377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
170477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
170577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
17066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
170777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
170877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
170977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
171077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
171377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
171477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
171577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
171677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
171777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
171877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
171977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
172077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
172177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
17221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
17261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
173277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
173477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
173577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
173677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
173777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
173877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
174177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
174277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
174377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
174477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
174577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1746b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1747b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1748b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
17496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
17506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
1751b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
175377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
175477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
175677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
175777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
175877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
175977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
176077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
176177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
176277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
177277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
177377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
177477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
177577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
177677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
177777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
177877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
177977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
178077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
178177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
178277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
17831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
17871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
178877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
178977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
179077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
179177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
179277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
179377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
179477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
179577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
179677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
179777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
179877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
179977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
180077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
1801b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
18026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
18036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
18046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
18056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
18066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
18076f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
18086f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
18096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
18106f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
18116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
18126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1813b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1814b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
1815b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1816b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1817b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1818b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1819b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1820b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1821b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1822b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1823b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1824b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1825b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
1826b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
1827b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
1828b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
1830b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
1831b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
1832b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
1833b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
1834b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
1835b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
1836b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1837b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1838b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1839b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
1840b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
1841b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = new Dialog(getContext());
1842b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
1843b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
1844b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
1845b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
1846b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
1847b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
1848b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
1849b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1850b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1851b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1852b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1853b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1854b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1855b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1856b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1857b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1858b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
1859b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1860b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1861b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1862b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1863b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
1864b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1865b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1866b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1867b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1868b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1869b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
1870b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
1871b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog = null;
1872b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1873b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1874b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1875b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
1876b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
1877b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
1878b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
1879b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
1880b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
1881b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
188277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
1883