RecipientEditTextView.java revision e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4
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;
70e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow;
71416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView;
72c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView;
73b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa
74a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList;
756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays;
76b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection;
776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections;
786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator;
7977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap;
80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet;
81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set;
82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/**
842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients.
862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */
87b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
88b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
89e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
90e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        PopupWindow.OnDismissListener {
91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private static final String TAG = "RecipientEditTextView";
93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private static final int CHIP_LIMIT = 2;
964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackground = null;
98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipDelete = null;
100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mChipPadding;
102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Tokenizer mTokenizer;
104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Drawable mChipBackgroundPressed;
106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip mSelectedChip;
108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int mAlternatesLayout;
110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private Bitmap mDefaultContactPhoto;
1121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
1134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private ImageSpan mMoreChip;
1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
115c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira    private TextView mMoreItem;
1164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
117054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
118a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira
119e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipHeight;
120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private float mChipFontSize;
122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira
1238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private Validator mValidator;
1248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
125045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    private Drawable mInvalidChipBackground;
126045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
127156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private Handler mHandler;
128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira    private static final long DISMISS_DELAY = 300;
132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira
133c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    private int mPendingChipsCount = 0;
134c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
13577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira    private static int sSelectedTextColor = -1;
13677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira
13705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
140c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
141e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
142dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListPopupWindow mAlternatesPopup;
144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListPopupWindow mAddressPopup;
1461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
14777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
14877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
1490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
151bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    private boolean mShouldShrink = true;
152bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    // Chip copy fields.
154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private GestureDetector mGestureDetector;
155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private Dialog mCopyDialog;
157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private int mCopyViewRes;
159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private String mCopyAddress;
161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
162b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
163a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
164a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler     * selected chip.
165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private OnItemClickListener mAlternatesListener;
167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
168416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private int mCheckedItem;
169e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private TextWatcher mTextWatcher;
170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
171416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private ScrollView mScrollView;
172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private boolean mTried;
174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void run() {
178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mTextWatcher == null) {
179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                mTextWatcher = new RecipientTextWatcher();
180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                addTextChangedListener(mTextWatcher);
181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    };
184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
18577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
18677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        @Override
190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        public void run() {
191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            handlePendingChips();
192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    };
195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
1962d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
1972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira        super(context, attrs);
19877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        if (sSelectedTextColor == -1) {
19977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
20077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        }
201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
202e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
2031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
204e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        mAddressPopup.setOnDismissListener(this);
2050436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        mCopyDialog = new Dialog(context);
206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            @Override
208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    long rowId) {
210368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
211e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira                setEnabled(true);
212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        .getRecipientEntry(position));
214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
215368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                delayed.obj = mAlternatesPopup;
216b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearComposingText();
218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        };
2206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
221c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        setOnItemClickListener(this);
2224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        setCustomSelectionActionModeCallback(this);
223156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        mHandler = new Handler() {
224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            @Override
225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            public void handleMessage(Message msg) {
226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                if (msg.what == DISMISS) {
227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
228156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                    return;
229156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                }
230156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira                super.handleMessage(msg);
231156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira            }
232156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira        };
2336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mTextWatcher = new RecipientTextWatcher();
2346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        addTextChangedListener(mTextWatcher);
235b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mGestureDetector = new GestureDetector(context, this);
236ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    }
237cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira
238ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    @Override
239ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
240ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        super.setAdapter(adapter);
241ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        if (adapter == null) {
242ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik            return;
243ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        }
2444fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
2464fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    @Override
2474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    public void onSelectionChanged(int start, int end) {
2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // When selection changes, see if it is inside the chips area.
2494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If so, move the cursor back after the chips again.
25083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        Spannable span = getSpannable();
25183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int textLength = getText().length();
25283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class);
25383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (chips != null && chips.length > 0) {
2544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            if (chips != null && chips.length > 0) {
2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira                // Grab the last chip and set the cursor to after it.
256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength));
2574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            }
258d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
259d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        super.onSelectionChanged(start, end);
260d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
261d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
262c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    /**
263c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
264c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
265c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * not already editable. Commas are excluded as they are added automatically
266c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     * by the view.
267c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira     */
268c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
269c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void append(CharSequence text, int start, int end) {
270001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        // We don't care about watching text changes while appending.
271001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (mTextWatcher != null) {
272001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            removeTextChangedListener(mTextWatcher);
273001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        }
274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.append(text, start, end);
275c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
2764f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira            final String displayString = (String) text;
27705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
278c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
279c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
280c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                mPendingChipsCount++;
281a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira                mPendingChips.add((String)text);
282c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            }
283c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
284a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
285a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount > 0) {
286a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            postHandlePendingChips();
287a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
2886ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        mHandler.post(mAddTextWatcher);
289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
291d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    @Override
292d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
293b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
294d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!hasFocus) {
2954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            shrink();
2964fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        } else {
2974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            expand();
298416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            scrollLineIntoView(getLineCount());
2994fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
3002d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
3012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
302b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    @Override
303b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    public void performValidation() {
304b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira        // Do nothing. Chips handles its own validation.
305b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira    }
306b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira
3074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void shrink() {
3085a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira        if (mSelectedChip != null
3095a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
3104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            clearSelectedChip();
3114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        } else {
312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // Reset any pending chips as they would have been handled
313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            // when the field lost focus.
314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            if (mPendingChipsCount > 0) {
315a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                postHandlePendingChips();
316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            } else {
317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                Editable editable = getText();
318001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int end = getSelectionEnd();
319001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
320001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
321001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                if ((chips == null || chips.length == 0)) {
322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(getText(), start);
323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // In the middle of chip; treat this as an edit
324001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    // and commit the whole token.
325001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    if (whatEnd != getSelectionEnd()) {
326001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        handleEdit(start, whatEnd);
327001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    } else {
328001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                        commitChip(start, end, editable);
329001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                    }
330001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira                }
331090139db34b366608b60e73f312833d84cf42259Mindy Pereira            }
332001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira            mHandler.post(mAddTextWatcher);
3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
3340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        createMoreChip();
3354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void expand() {
3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        removeMoreChip();
3394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setCursorVisible(true);
3404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable text = getText();
3414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
34277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
34377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // has expanded the field.
34477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
34577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            new RecipientReplacementTask().execute();
34677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            mTemporaryRecipients = null;
34777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
3484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
3494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
351e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        paint.setTextSize(mChipFontSize);
352c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
353c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
354c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        }
355c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
356c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira                TextUtils.TruncateAt.END);
3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
358c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
359e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // on the sides.
363e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int deleteWidth = height;
3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint,
3661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(true) - deleteWidth);
3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
3681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // tap a chip without difficulty.
3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + deleteWidth);
373c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        // Create the background of the chip.
3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
3771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackgroundPressed != null) {
3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipBackgroundPressed.draw(canvas);
38077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira            paint.setColor(sSelectedTextColor);
38197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
38297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
38397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            // Make the delete a square.
3851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.setBounds(width - deleteWidth, 0, width, height);
3861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            mChipDelete.draw(canvas);
3871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
3881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
3891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
3901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
3911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
393045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
394045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
395045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Get the background drawable for a RecipientChip.
396045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
397045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    public Drawable getChipBackground(RecipientEntry contact) {
398a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
399045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira                mChipBackground : mInvalidChipBackground;
400045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    }
401045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira
402e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // on the sides.
406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira        int height = (int) mChipHeight;
4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int iconWidth = height;
4084221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        String displayText =
4094221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() :
4104221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : "";
4114221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence ellipsizedText = ellipsizeText(displayText, paint,
4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                calculateAvailableWidth(false) - iconWidth);
4131a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4141a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira        // tap a chip without difficulty.
4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                ellipsizedText.length()))
4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                + (mChipPadding * 2) + iconWidth);
418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
419c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Create the background of the chip.
420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
422045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        Drawable background = getChipBackground(contact);
423045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        if (background != null) {
424045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.setBounds(0, 0, width, height);
425045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            background.draw(canvas);
4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4279024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            // Don't draw photos for recipients that have been typed in.
4281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
4299024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
43090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // There may not be a photo yet if anything but the first contact address
43190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                // was selected.
43290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
43390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    // TODO: cache this in the recipient entry?
43490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
43590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                            .getPhotoThumbnailUri());
43690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                    photoBytes = contact.getPhotoBytes();
43790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira                }
43890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira
4399024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Bitmap photo;
4409024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                if (photoBytes != null) {
4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
4429024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                } else {
4439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    // TODO: can the scaled down default photo be cached?
4449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                    photo = mDefaultContactPhoto;
4459024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                }
4469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Draw the photo on the left side.
4479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                Matrix matrix = new Matrix();
4489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
449379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira                RectF dst = new RectF(width - iconWidth, 0, width, height);
4509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
4519024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                canvas.drawBitmap(photo, matrix, paint);
452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
4539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
4549024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira                iconWidth = 0;
455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
456e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
45797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira            // Vertically center the text in the chip.
458379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
45997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } else {
4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return tmpBitmap;
4641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    }
465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
46697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
46797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        Rect bounds = new Rect();
46897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        paint.getTextBounds((String)text, 0, text.length(), bounds);
46997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
47097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return height - ((height - textHeight) / 2);
47197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira    }
47297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira
4731e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira    public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
4741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throws NullPointerException {
4751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (mChipBackground == null) {
4761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira            throw new NullPointerException(
4771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
4791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Layout layout = getLayout();
480c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
4811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        TextPaint paint = getPaint();
4821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        float defaultSize = paint.getTextSize();
48377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        int defaultColor = paint.getColor();
4841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira
4851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        Bitmap tmpBitmap;
4861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        if (pressed) {
487e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
4891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        } else {
490045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
4911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        }
492c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
493c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
494c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
4951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
49677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Return text to the original size.
498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        paint.setTextSize(defaultSize);
49977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira        paint.setColor(defaultColor);
500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return recipientChip;
5012d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5022d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
5038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
504045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
505045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * 1) which line the chip appears on
5065519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 2) the height of a chip
5075519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira     * 3) padding built into the edit text view
5088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
509c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira    private int calculateOffsetFromBottom(int line) {
5105519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira        // Line offsets start at zero.
511416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        int actualLine = getLineCount() - (line + 1);
51297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
51397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira                + getDropDownVerticalOffset();
514f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira    }
515f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira
5168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
5178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
5188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
5198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * that will be added to the chip.
5208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
5242d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
5264f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * Set all chip dimensions and resources. This has to be done from the
5274f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * application as this is a static library.
5284f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param chipBackground
5291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipBackgroundPressed
5304f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira     * @param invalidChip
5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param chipDelete
5321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param defaultContact
5331426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param moreResource
5341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira     * @param alternatesLayout
5351426d714da911130b643f7039590eceaa6420aa7Mindy Pereira     * @param chipHeight
536c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * @param padding Padding around the text in a chip
537b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param chipFontSize
538b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira     * @param copyViewRes
539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
54043876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira    public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed,
541045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira            Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource,
542d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira            int alternatesLayout, float chipHeight, float padding,
543b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            float chipFontSize, int copyViewRes) {
544b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackground = chipBackground;
545b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipBackgroundPressed = chipBackgroundPressed;
546b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipDelete = chipDelete;
547b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipPadding = (int) padding;
548b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mAlternatesLayout = alternatesLayout;
549b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mDefaultContactPhoto = defaultContact;
550b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null);
551b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipHeight = chipHeight;
552b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mChipFontSize = chipFontSize;
553b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mInvalidChipBackground = invalidChip;
554b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyViewRes = copyViewRes;
555b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
556b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
557bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    /**
558bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * Set whether to shrink the recipients field such that at most
559bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * one line of recipients chips are shown when the field loses
560bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * focus. By default, the number of displayed recipients will be
561bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
562bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     * @param shrink
563bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira     */
564bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
565bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        mShouldShrink = shrink;
566bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira    }
567bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
568c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
569c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
570c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
5717bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (width != 0 && height != 0 && mPendingChipsCount > 0) {
5727bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            postHandlePendingChips();
5737bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
57477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        // Try to find the scroll view parent, if it exists.
575416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView == null && !mTried) {
576416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            ViewParent parent = getParent();
577416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
578416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                parent = parent.getParent();
579416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
580416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            if (parent != null) {
581416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                mScrollView = (ScrollView) parent;
582416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            }
583416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira            mTried = true;
584416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
585c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    }
586c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira
587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    private void postHandlePendingChips() {
588a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
589a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        mHandler.post(mHandlePendingChips);
590a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira    }
591a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira
5920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void handlePendingChips() {
593a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        if (mPendingChipsCount <= 0) {
594a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            return;
595a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        }
5967bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        if (getWidth() <= 0) {
5977bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // The widget has not been sized yet.
5987bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // This will be called as a result of onSizeChanged
5997bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            // at a later point.
6007bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira            return;
6017bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira        }
602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira        synchronized (mPendingChips) {
603a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount);
604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            Editable editable = getText();
605a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            // Tokenize!
606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            for (int i = 0; i < mPendingChips.size(); i++) {
607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                String current = mPendingChips.get(i);
608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenStart = editable.toString().indexOf(current);
609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                int tokenEnd = tokenStart + current.length();
610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (tokenStart >= 0) {
611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // When we have a valid token, include it with the token
612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // to the left.
613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    if (tokenEnd < editable.length() - 2
614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                        tokenEnd++;
616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    }
617a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createReplacementChip(tokenStart, tokenEnd, editable);
6180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mPendingChipsCount--;
6200fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            sanitizeSpannable();
622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            if (mTemporaryRecipients != null
623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
625a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    new RecipientReplacementTask().execute();
626a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mTemporaryRecipients = null;
627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                } else {
628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    // Create the "more" chip
629a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
630a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
631a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
63277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
633a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                    createMoreChip();
634a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                }
635a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            } else {
636a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // There are too many recipients to look up, so just fall back
637a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // to
638a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                // showing addresses for all of them.
639a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira                mTemporaryRecipients = null;
64077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                createMoreChip();
64177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
642a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChipsCount = 0;
643a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira            mPendingChips.clear();
6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Remove any characters after the last valid chip.
6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void sanitizeSpannable() {
6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientChip[] chips = getRecipients();
6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (chips != null && chips.length > 0) {
6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int end;
6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            ImageSpan lastSpan;
6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (mMoreChip != null) {
6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = mMoreChip;
6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            } else {
6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                lastSpan = chips[chips.length - 1];
6600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            Editable editable = getText();
6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            int length = editable.length();
6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            if (length > end) {
6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                // See what characters occur after that and eliminate them.
6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                            + editable);
6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                }
6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                editable.delete(end + 1, length);
6710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            }
6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
6740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
6750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    /**
6760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
6770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
6780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira     */
6790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
6801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
6811e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // There is already a chip present at this location.
6821e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // Don't recreate it.
6831e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return;
6841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        }
685b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
6861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
6870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (commitCharIndex == token.length() - 1) {
6880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            token = token.substring(0, token.length() - 1);
6890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
6900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
691d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (entry != null) {
692d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            String destText = entry.getDestination();
693d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            destText = (String) mTokenizer.terminateToken(destText);
694d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Always leave a blank space at the end of a chip.
695d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int textLength = destText.length() - 1;
696d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            SpannableString chipText = new SpannableString(destText);
697d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int end = getSelectionEnd();
698d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            int start = mTokenizer.findTokenStart(getText(), end);
699d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            RecipientChip chip = null;
700d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            try {
701d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chip = constructChipSpan(entry, start, false);
702d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
703d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            } catch (NullPointerException e) {
704d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                Log.e(TAG, e.getMessage(), e);
705d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
707d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
708d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            // Add this chip to the list of entries "to replace"
709d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            if (chip != null) {
7106f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                chip.setOriginalText(chipText.toString());
711d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                mTemporaryRecipients.add(chip);
712d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
71377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
7140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
7160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
717d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        if (TextUtils.isEmpty(token)) {
718d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            return null;
719d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira        }
7200fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
7211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        String display = null;
7226ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
723454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // If we can get a name from tokenizing, then generate an entry from
724454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira            // this.
7251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            display = tokens[0].getName();
7261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (!TextUtils.isEmpty(display)) {
7271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
7286ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira            } else {
7296ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                display = tokens[0].getAddress();
7306ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                if (!TextUtils.isEmpty(display)) {
7316ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
7326ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira                }
7331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
7341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        }
7356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Unable to validate the token or to create a valid token from it.
7366ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        // Just create a chip the user can edit.
7376ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
7386ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            // Try fixing up the entry using the validator.
7396ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            token = mValidator.fixText(token).toString();
740490556a764a879cd0eaff358e90705cc1335c92eErik            if (!TextUtils.isEmpty(token)) {
741490556a764a879cd0eaff358e90705cc1335c92eErik                // protect against the case of a validator with a null domain,
742490556a764a879cd0eaff358e90705cc1335c92eErik                // which doesn't add a domain to the token
743490556a764a879cd0eaff358e90705cc1335c92eErik                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token);
744490556a764a879cd0eaff358e90705cc1335c92eErik                if (tokenized.length > 0) {
745490556a764a879cd0eaff358e90705cc1335c92eErik                    token = tokenized[0].getAddress();
746490556a764a879cd0eaff358e90705cc1335c92eErik                }
747d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira            }
7486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        }
749454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
7501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return RecipientEntry.constructFakeEntry(token);
7511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
7521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
7536ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    private boolean isValid(String text) {
7546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
7556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira    }
7566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira
7571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private String tokenizeAddress(String destination) {
7581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
7591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (tokens != null && tokens.length > 0) {
7601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return tokens[0].getAddress();
7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
7621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return destination;
7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
765c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira    @Override
766c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
767c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        mTokenizer = tokenizer;
768c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.setTokenizer(mTokenizer);
769c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
770c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    @Override
7728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    public void setValidator(Validator validator) {
7738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        mValidator = validator;
7748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira        super.setValidator(validator);
7758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    }
7768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira
7778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
7798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * we override onItemClickListener so we can get all the associated
7808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information including display text, address, and id.
7818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
782c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
783c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void replaceText(CharSequence text) {
784c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return;
785c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
786c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
7878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
7898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
790c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
79195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
79295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
79395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira            clearSelectedChip();
79495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        }
79595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira        return super.onKeyPreIme(keyCode, event);
79695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    }
79795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira
7988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
7998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor key presses in this view to see if the user types
8008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
8018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has contact matches and types
8028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
8038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the user has entered text that has no contact matches and types
8048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * a commit key, then create a chip from the text they have entered.
8058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
80695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira    @Override
807c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
808c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        switch (keyCode) {
809c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_ENTER:
810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (event.hasNoModifiers()) {
812d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                    if (commitDefault()) {
813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        return true;
814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
815e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
816e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
817e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
818e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                    } else if (focusNext()) {
819e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira                        return true;
820e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
82295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira                break;
823e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            case KeyEvent.KEYCODE_TAB:
824e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                if (event.hasNoModifiers()) {
825e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (mSelectedChip != null) {
826e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        clearSelectedChip();
827e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    } else {
828e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        commitDefault();
829e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
830e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    if (focusNext()) {
831e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                        return true;
832e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                    }
833e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira                }
834c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
835c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyUp(keyCode, event);
836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
837c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
838e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    private boolean focusNext() {
839e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
840e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        if (next != null) {
841e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            next.requestFocus();
842e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira            return true;
843e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        }
844e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira        return false;
845e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira    }
846e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira
847045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira    /**
848045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
849045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
850045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
851045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * should search for a token to turn into a chip.
852045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * @return If a chip was created from a real contact.
853045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     */
8548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    private boolean commitDefault() {
8554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Editable editable = getText();
856045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira        int end = getSelectionEnd();
8574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
858dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira
859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // In the middle of chip; treat this as an edit
862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // and commit the whole token.
863e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (whatEnd != getSelectionEnd()) {
864e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                handleEdit(start, whatEnd);
865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return true;
8664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return commitChip(start, end , editable);
868e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        return false;
870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void commitByCharacter() {
873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int end = getSelectionEnd();
875e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        if (shouldCreateChip(start, end)) {
877e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            commitChip(start, end, editable);
8784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
879054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        setSelection(getText().length());
880e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
8814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
883001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira        if (getAdapter().getCount() > 0 && enoughToFilter()) {
884e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // choose the first entry.
885e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            submitItemAtPosition(0);
886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            dismissDropDown();
887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            return true;
888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        } else {
889e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
890e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            clearComposingText();
892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
8931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
894d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                if (entry != null) {
895d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
896d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    CharSequence chipText = createChip(entry, false);
897d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                    editable.replace(start, end, chipText);
898d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira                }
899d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira                dismissDropDown();
9004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                return true;
901d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            }
902d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
9034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
904d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira    }
905d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira
906e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private boolean shouldCreateChip(int start, int end) {
9071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
9081e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    }
9091e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira
9101e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
9111e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
9121e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        if ((chips == null || chips.length == 0)) {
9131e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            return false;
91405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira        }
9151e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira        return true;
916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private void handleEdit(int start, int end) {
919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // This is in the middle of a chip, so select out the whole chip
920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        // and commit it.
921e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        Editable editable = getText();
922e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        setSelection(end);
923e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        String text = getText().toString().substring(start, end);
924e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
925e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
926e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        CharSequence chipText = createChip(entry, false);
927e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        editable.replace(start, getSelectionEnd(), chipText);
928054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira        dismissDropDown();
92905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira    }
93005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira
9318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If there is a selected chip, delegate the key events
9338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * to the selected chip.
9348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
937b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
938b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
939b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesPopup.dismiss();
940b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
941b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            removeChip(mSelectedChip);
942c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
944c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
945c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return true;
946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
947c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return super.onKeyDown(keyCode, event);
9492d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira    }
9502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira
951c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private Spannable getSpannable() {
952ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik        return getText();
953c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
954c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
955b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipStart(RecipientChip chip) {
956b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanStart(chip);
957b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
958b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
959b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private int getChipEnd(RecipientChip chip) {
960b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return getSpannable().getSpanEnd(chip);
961b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
962b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * Instead of filtering on the entire contents of the edit box,
965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * this subclass method filters on the range from
966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     * and makes sure that the range is not already a Chip.
969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (enoughToFilter()) {
973c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int end = getSelectionEnd();
974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // If this is a RecipientChip, don't filter
976c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // on its contents.
977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Spannable span = getSpannable();
978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
979c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (chips != null && chips.length > 0) {
980c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return;
981c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
982c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
983c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        super.performFiltering(text, keyCode);
984c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
985c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
986c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void clearSelectedChip() {
987c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (mSelectedChip != null) {
988b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            unselectChip(mSelectedChip);
989c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            mSelectedChip = null;
990c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
99136d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira        setCursorVisible(true);
992c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
993c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
9948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
9958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
9968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If the view does not have focus, any tap on the view
9978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * will just focus the view. If the view has focus, determine
9988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
9998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * is not selected, select it and clear any other selected chips.
10008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * If it isn't, then select that chip.
10018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
1004d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        if (!isFocused()) {
1005d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            // Ignore any chip taps until this view is focused.
1006d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira            return super.onTouchEvent(event);
1007d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        }
1008c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean handled = super.onTouchEvent(event);
1009d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira        int action = event.getAction();
1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        boolean chipWasSelected = false;
10116caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira        if (mSelectedChip == null) {
1012b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            mGestureDetector.onTouchEvent(event);
1013b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
10140436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float x = event.getX();
1016c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            float y = event.getY();
1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1018c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip currentChip = findChip(offset);
1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (currentChip != null) {
1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1022c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                        clearSelectedChip();
1023b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1024c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else if (mSelectedChip == null) {
10258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        setSelection(getText().length());
10268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira                        commitDefault();
1027b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    } else {
1029b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    }
1031c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                }
1032c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                chipWasSelected = true;
1033416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira                handled = true;
1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1036c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1037c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            clearSelectedChip();
1038c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1039c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return handled;
1040c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1041c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1042416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    private void scrollLineIntoView(int line) {
1043416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        if (mScrollView != null) {
1044c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1045416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        }
1046416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira    }
1047416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira
1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int width, Context context) {
1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
1051c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
1052b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Align the alternates popup with the left side of the View,
1053b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // regardless of the position of the chip tapped.
1054b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setWidth(width);
1055e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
105677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        alternatesPopup.setAnchorView(this);
1057416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
1058b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
1059368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
1060e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        // Clear the checked item.
1061e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = -1;
1062b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        alternatesPopup.show();
1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = alternatesPopup.getListView();
1064b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1065b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Checked item would be -1 if the adapter has not
1066b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // loaded the view that should be checked yet. The
1067b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
1068b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // is called in a separate thread.
1069b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mCheckedItem != -1) {
1070b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
1071b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mCheckedItem = -1;
1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1073b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1074b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1075e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    // Dismiss listener for alterns and single address popup.
1076e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    @Override
1077e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    public void onDismiss() {
1078e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(true);
1079e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira    }
1080e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira
1081b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
1082b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
1083b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                mAlternatesLayout, this);
1084b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1085b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
10861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
10871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
10881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                .getEntry());
10891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
10901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1091a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler    @Override
1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onCheckedItemChanged(int position) {
1093b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
1094b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            listView.setItemChecked(position, true);
1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        }
1097e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira        mCheckedItem = position;
1098b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1099b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira
1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    // what comes before the finger.
1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private int putOffsetInRange(int o) {
1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int offset = o;
1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable text = getText();
1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int length = text.length();
1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Remove whitespace from end to find "real end"
1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int realLength = length;
1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            if (text.charAt(i) == ' ') {
1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                realLength--;
1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            } else {
1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                break;
1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11174fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // If the offset is beyond or at the end of the text,
11184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        // leave it alone.
11194fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (offset >= realLength) {
1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return offset;
1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
11224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        Editable editable = getText();
1123c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            // Keep walking backward!
1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            offset--;
1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return offset;
1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11304fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    private int findText(Editable text, int offset) {
11314fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        if (text.charAt(offset) != ' ') {
11324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira            return offset;
11334fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        }
11344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira        return -1;
11354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira    }
11364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira
1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private RecipientChip findChip(int offset) {
1138c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Find the chip that contains this offset.
1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        for (int i = 0; i < chips.length; i++) {
1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            RecipientChip chip = chips[i];
1142b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(chip);
1143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(chip);
1144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (offset >= start && offset <= end) {
1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                return chip;
1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return null;
1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1151fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1152c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira        String displayText = entry.getDestination();
11534f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira        displayText = (String) mTokenizer.terminateToken(displayText);
1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        // Always leave a blank space at the end of a chip.
1155b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira        int textLength = displayText.length()-1;
1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        SpannableString chipText = new SpannableString(displayText);
1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        try {
11606f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip chip = constructChipSpan(entry, start, pressed);
11616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chipText.setSpan(chip, 0, textLength,
1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
11636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            chip.setOriginalText(chipText.toString());
1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        } catch (NullPointerException e) {
1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            Log.e(TAG, e.getMessage(), e);
1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            return null;
1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return chipText;
1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
11728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
11738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
11748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * contact information of the selected item.
11758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    @Override
1177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        submitItemAtPosition(position);
1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1181c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    private void submitItemAtPosition(int position) {
11820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        RecipientEntry entry = createValidatedEntry(
11830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
11841e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        if (entry == null) {
11851e85502fdc04a44f76ffa9904be9ab6ab80292ceErik            return;
11861e85502fdc04a44f76ffa9904be9ab6ab80292ceErik        }
1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        clearComposingText();
1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int end = getSelectionEnd();
1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        Editable editable = getText();
1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
11944221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        CharSequence chip = createChip(entry, false);
11954221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        if (chip != null) {
11964221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira            editable.replace(start, end, chip);
11974221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira        }
1198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1199c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
12010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        if (item == null) {
12020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return null;
12030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
12040fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        final RecipientEntry entry;
12050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // If the display name and the address are the same, or if this is a
12060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
12070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        // recipient that is editable.
12080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        String destination = item.getDestination();
1209c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira        if (TextUtils.isEmpty(item.getDisplayName())
1210c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira                || TextUtils.equals(item.getDisplayName(), destination)
12110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                || (mValidator != null && !mValidator.isValid(destination))) {
12120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
12130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        } else {
12140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            entry = item;
12150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        }
12160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        return entry;
12170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    }
12180fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira
1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getContactIds() {
1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
12227a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        RecipientChip[] chips = getRecipients();
12237a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12247a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12257a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getContactId());
12267a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1227c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1228c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1229c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1230c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
123183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    private RecipientChip[] getRecipients() {
123283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        return getSpannable().getSpans(0, getText().length(), RecipientChip.class);
123383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira    }
123483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira
12356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private RecipientChip[] getSortedRecipients() {
12366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
12376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                .asList(getRecipients()));
12386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        final Spannable spannable = getSpannable();
12396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
12406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
12416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            @Override
12426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
12436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int firstStart = spannable.getSpanStart(first);
12446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int secondStart = spannable.getSpanStart(second);
12456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                if (firstStart < secondStart) {
12466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return -1;
12476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else if (firstStart > secondStart) {
12486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 1;
12496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                } else {
12506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    return 0;
12516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                }
12526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
12536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        });
12546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
12556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
12566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1257c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /* package */ Collection<Long> getDataIds() {
1259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        final Set<Long> result = new HashSet<Long>();
126083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        RecipientChip [] chips = getRecipients();
12617a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira        if (chips != null) {
12627a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            for (RecipientChip chip : chips) {
12637a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira                result.add(chip.getDataId());
12647a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira            }
1265c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1266c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        return result;
1267c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    }
1268c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
12694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
12724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
12774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
12814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
12858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * No chips are selectable.
12868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    @Override
12884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
12894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        return false;
12904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
12914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
12928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
1293045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1294045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * do not fit in the pre-defined available space when the
1295045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira     * RecipientEditTextView loses focus.
12968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
12970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira    private void createMoreChip() {
1298bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        if (!mShouldShrink) {
1299bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira            return;
1300bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira        }
1301bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira
13026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
13036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        if (tempMore.length > 0) {
13046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
13056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
13066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
130783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
13080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
13090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
13104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
13116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Spannable spannable = getSpannable();
131283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira        int numRecipients = recipients.length;
13134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1314c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), overage);
1315c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1316c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1317c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1318c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1319c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                + mMoreItem.getPaddingRight();
13204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int height = getLineHeight();
13214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
13224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Canvas canvas = new Canvas(drawable);
13234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0),
1324c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira                morePaint);
13254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
13274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        result.setBounds(0, 0, width, height);
13286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        MoreImageSpan moreSpan = new MoreImageSpan(result);
13294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        // Remove the overage chips.
1330368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        if (recipients == null || recipients.length == 0) {
13314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Log.w(TAG,
13320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    "We have recipients. Tt should not be possible to have zero RecipientChips.");
13330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            mMoreChip = null;
13340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira            return;
13354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
1336368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
13374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceStart = 0;
13384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        int totalReplaceEnd = 0;
13396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        Editable text = getText();
1340368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
1341368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            mRemovedSpans.add(recipients[i]);
13429024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            if (i == numRecipients - overage) {
1343368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
13449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
1345368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (i == recipients.length - 1) {
1346368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
13479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira            }
13486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
13496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
13506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
13516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
135277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
1353368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            spannable.removeSpan(recipients[i]);
13544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
135577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
135677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
135777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
13584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
135977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        text.replace(start, end, chipText);
13600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira        mMoreChip = moreSpan;
13614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
13624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
13638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira    /**
13648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
13658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
13668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira     */
13674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    private void removeMoreChip() {
13684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        if (mMoreChip != null) {
13694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            Spannable span = getSpannable();
13704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            span.removeSpan(mMoreChip);
13714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            mMoreChip = null;
13724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            // Re-add the spans that were removed.
13734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
13744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                // Recreate each removed span.
137564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                RecipientChip[] recipients = getRecipients();
137664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // Start the search for tokens after the last currently visible
137764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                // chip.
137864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
13794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                Editable editable = getText();
13804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
13816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    int chipStart;
13820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    int chipEnd;
13830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira                    String token;
13846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // Need to find the location of the chip, again.
13856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    token = (String) chip.getOriginalText();
138664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // As we find the matching recipient for the remove spans,
138764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // reduce the size of the string we need to search.
138864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // That way, if there are duplicates, we always find the correct
138964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    // recipient.
139064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    chipStart = editable.toString().indexOf(token, end);
13916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                    // -1 for the space!
139264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
1393bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    // Only set the span if we found a matching token.
1394bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    if (chipStart != -1) {
1395bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
1396bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1397bbf141b605f92729864984229e6e1062d9a462beMindy Pereira                    }
13984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                }
13994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira                mRemovedSpans.clear();
14004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira            }
14014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira        }
14024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira    }
14034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira
1404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira    /**
1405b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
1406b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
1407b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
1408b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
1409b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * will change the background color of the chip, show the delete icon,
1410b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * and a popup window with the address in use highlighted and any other
1411b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * alternate addresses for the contact.
1412b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @param currentChip Chip to select.
1413b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
1414b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * just contained an email address.
1415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira     */
1416b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public RecipientChip selectChip(RecipientChip currentChip) {
14171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
14181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            CharSequence text = currentChip.getValue();
14191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            Editable editable = getText();
14201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            removeChip(currentChip);
14211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            editable.append(text);
14221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setCursorVisible(true);
14231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            setSelection(editable.length());
14242f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
14251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
1426b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int start = getChipStart(currentChip);
1427b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            int end = getChipEnd(currentChip);
1428b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            getSpannable().removeSpan(currentChip);
1429b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            RecipientChip newChip;
1430b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            try {
1431b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
1432b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } catch (NullPointerException e) {
1433b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1434b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                return null;
1435b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            }
1436fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira            Editable editable = getText();
1437b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
143883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            if (start == -1 || end == -1) {
1439b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
144083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            } else {
14418b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
144283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira            }
1443b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            newChip.setSelected(true);
14441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
1446c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira            }
14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
14486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
1449b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return newChip;
1450b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
14511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int start = getChipStart(currentChip);
14521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int end = getChipEnd(currentChip);
14531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            getSpannable().removeSpan(currentChip);
14541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            RecipientChip newChip;
14551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            try {
14561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
14571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } catch (NullPointerException e) {
14581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.e(TAG, e.getMessage(), e);
14591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                return null;
14601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
1461b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Editable editable = getText();
14621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
14631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (start == -1 || end == -1) {
14641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
14651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            } else {
14661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
14671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            newChip.setSelected(true);
14691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
14701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
14711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
14736ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira            setCursorVisible(false);
14741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            return newChip;
1475fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira        }
1476b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1477fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira
1478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
14791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
14801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            int width, Context context) {
14811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
14821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
14831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // Align the alternates popup with the left side of the View,
14841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        // regardless of the position of the chip tapped.
1485e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira        setEnabled(false);
14861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setWidth(width);
14871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAnchorView(this);
14881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setVerticalOffset(bottom);
14891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
14901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
14911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            @Override
14921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
14931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                unselectChip(currentChip);
14941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                popup.dismiss();
14951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            }
14961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        });
14971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        popup.show();
14981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        ListView listView = popup.getListView();
14991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
15001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira        listView.setItemChecked(0, true);
15011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira    }
15021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira
1503b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1504b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1505b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * the chip without a delete icon and with an unfocused background. This
1506b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * is called when the RecipientChip no longer has focus.
1507b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void unselectChip(RecipientChip chip) {
1509b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1512c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira        mSelectedChip = null;
1513b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
15142f5589283d93933751c20791ef42dc7eab87061aMindy Pereira            Log.e(TAG, "The chip being unselected no longer exists.");
1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1516c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira            getSpannable().removeSpan(chip);
1517b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
15188b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            editable.removeSpan(chip);
15198b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            try {
15208b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
15218b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
15228b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            } catch (NullPointerException e) {
15238b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira                Log.e(TAG, e.getMessage(), e);
15248b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira            }
1525c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setSelection(editable.length());
1528b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
1529b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mAlternatesPopup.dismiss();
1530c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1532c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1533c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether this chip contains the position passed in.
1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public boolean matchesChip(RecipientChip chip, int offset) {
1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1540b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            return false;
1542c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return (offset >= start && offset <= end);
1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1547b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Return whether a touch event was inside the delete target of
1549b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * a selected chip. It is in the delete target if:
1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 1) the x and y points of the event are within the
1551b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * delete assset.
1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
1553b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * right after the selected chip.
1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * @return boolean
1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1556b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
1557b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Figure out the bounds of this chip and whether or not
1558b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // the user clicked in the X portion.
1559b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
15613656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira
1562b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
1564b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    private void removeChip(RecipientChip chip) {
1566b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Spannable spannable = getSpannable();
1567b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
1568b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
1569b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable text = getText();
1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int toDelete = spanEnd;
1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Clear that there is a selected chip before updating any text.
1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1575c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1576b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        // Always remove trailing spaces when removing a chip.
15778b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
1578b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            toDelete++;
1579c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1580b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        spannable.removeSpan(chip);
1581b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        text.delete(spanStart, toDelete);
1582b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1583b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1584c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1585b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1587b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Replace this currently selected chip with a new chip
1589b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * that uses the contact data provided.
1590b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1591b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void replaceChip(RecipientChip chip, RecipientEntry entry) {
1592b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
1593b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            mSelectedChip = null;
1595c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int start = getChipStart(chip);
1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        int end = getChipEnd(chip);
1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        getSpannable().removeSpan(chip);
1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        Editable editable = getText();
1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        CharSequence chipText = createChip(entry, false);
1601b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (start == -1 || end == -1) {
1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            Log.e(TAG, "The chip to replace does not exist but should.");
1603b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            editable.insert(0, chipText);
1604b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        } else {
1605b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // There may be a space to replace with this chip's new associated
1606b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            // space. Check for it.
1607b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            int toReplace = end;
1608b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            while (toReplace >= 0 && toReplace < editable.length()
1609b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                    && editable.charAt(toReplace) == ' ') {
1610b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                toReplace++;
1611b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            }
1612b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira            editable.replace(start, toReplace, chipText);
1613c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1614b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        setCursorVisible(true);
1615b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (wasSelected) {
1616b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            clearSelectedChip();
1617c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1618b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1619c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1620b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    /**
1621b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
1622b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
1623b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     * Otherwise, unselect the chip.
1624b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira     */
1625b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira        if (chip.isSelected()) {
1627b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
1628b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                removeChip(chip);
1629b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira            } else {
1630b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira                clearSelectedChip();
1631c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira            }
1632c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira        }
1633b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira    }
1634c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira
1635368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    private boolean chipsPending() {
1636368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
1637368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira    }
1638368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira
1639311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    @Override
1640311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1641311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        mTextWatcher = null;
1642311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira        super.removeTextChangedListener(watcher);
1643311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira    }
1644311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira
1645e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    private class RecipientTextWatcher implements TextWatcher {
1646e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1647e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void afterTextChanged(Editable s) {
16481e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // If the text has been set to null or empty, make sure we remove
16491e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            // all the spans we applied.
16501e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            if (TextUtils.isEmpty(s)) {
16511e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                // Remove all the chips spans.
16521e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                Spannable spannable = getSpannable();
16531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
16541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                        RecipientChip.class);
16551e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                for (RecipientChip chip : chips) {
16561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(chip);
16571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                if (mMoreChip != null) {
16591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                    spannable.removeSpan(mMoreChip);
16601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                }
16611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira                return;
16621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira            }
16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Get whether there are any recipients pending addition to the
16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // view. If there are, don't do anything in the text watcher.
1665368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira            if (chipsPending()) {
1666e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                return;
1667e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1668e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (mSelectedChip != null) {
1669e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setCursorVisible(true);
1670e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                setSelection(getText().length());
1671e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                clearSelectedChip();
1672e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1673e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            int length = s.length();
1674e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            // Make sure there is content there to parse and that it is
1675054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira            // not just the commit character.
1676e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            if (length > 1) {
1677054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                char last;
167876ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
1679054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                int len = length() - 1;
1680054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                if (end != len) {
1681054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(end);
1682054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                } else {
1683054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                    last = s.charAt(len);
1684054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira                }
1685e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
1686e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    commitByCharacter();
1687e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
1688e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // Check if this is a valid email address. If it is,
1689e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    // commit it.
1690e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String text = getText().toString();
1691e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
1692e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
1693e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                            tokenStart));
1694e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    if (mValidator != null && mValidator.isValid(sub)) {
1695e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                        commitByCharacter();
1696e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                    }
1697e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira                }
1698e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira            }
1699e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1700e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1701e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1702e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira            // Do nothing.
1704e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1705e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira
1706e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        @Override
1707e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1708e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira        }
1709e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira    }
171077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
171377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String displayText = entry.getDestination();
17146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            if (displayText.indexOf(",") == -1) {
17156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                displayText = (String) mTokenizer.terminateToken(displayText);
17166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            }
171777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            try {
171877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return constructChipSpan(entry, -1, false);
171977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            } catch (NullPointerException e) {
172077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
172177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                return null;
172277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
172577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
172677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Void... params) {
172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mIndividualReplacements != null) {
172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mIndividualReplacements.cancel(true);
172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
173277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
17346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
173577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
173677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.add(existingChips[i]);
173777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
173877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (mRemovedSpans != null) {
173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
174177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
174277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
174377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
174477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
174577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
174677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
174777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
174877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
174977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                RecipientEntry entry = null;
17501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
175177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
175277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
17531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
17541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .getDestination())));
175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
175677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                if (entry != null) {
175777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(createFreeChip(entry));
175877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                } else {
175977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    replacements.add(temp);
176077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
176177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
176277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            if (replacements != null && replacements.size() > 0) {
176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                mHandler.post(new Runnable() {
176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    @Override
176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    public void run() {
176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                .toString());
176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable oldText = getText();
176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int start, end;
177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        int i = 0;
177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
177277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            start = oldText.getSpanStart(chip);
177377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            if (start != -1) {
1774b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                end = oldText.getSpanEnd(chip);
1775b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                text.removeSpan(chip);
1776b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                // Leave a spot for the space!
17776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                RecipientChip replacement = replacements.get(i);
17786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                text.setSpan(replacement, start, end,
1779b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
17806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
178177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
178277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            i++;
178377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        }
178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        Editable editable = getText();
178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.clear();
178677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        editable.insert(0, text);
178777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        originalRecipients.clear();
178877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
178977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                });
179077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
179177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
179277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
179377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
179477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira
179577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
179677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @SuppressWarnings("unchecked")
179777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        @Override
179877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        protected Void doInBackground(Object... params) {
179977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // For each chip in the list, look up the matching contact.
180077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // If there is a match, replace that chip with the matching
180177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            // chip.
180277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
180377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                (ArrayList<RecipientChip>) params[0];
180477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
180577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
180677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                addresses[i] = originalRecipients.get(i).getEntry().getDestination();
180777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
180877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
180977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
181077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
18111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
181277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
181377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    // Replace this.
18141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
18151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination())));
181677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    if (entry != null) {
181777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        mHandler.post(new Runnable() {
181877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            @Override
181977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            public void run() {
182077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                                replaceChip(temp, entry);
182177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                            }
182277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                        });
182377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                    }
182477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira                }
182577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            }
182677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira            return null;
182777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira        }
182877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira    }
1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
18306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
18316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    /**
18326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
18336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     * more chip across activity restarts/
18346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira     */
18356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    private class MoreImageSpan extends ImageSpan {
18366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        public MoreImageSpan(Drawable b) {
18376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira            super(b);
18386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira        }
18396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira    }
18406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira
1841b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1842b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onDown(MotionEvent e) {
1843b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1844b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1845b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1846b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1847b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
1848b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1849b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1850b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1851b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1852b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1853b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onLongPress(MotionEvent event) {
1854b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (mSelectedChip != null) {
1855b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            return;
1856b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1857b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float x = event.getX();
1858b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        float y = event.getY();
1859b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
1860b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        RecipientChip currentChip = findChip(offset);
1861b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        if (currentChip != null) {
1862b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            // Copy the selected chip email address.
1863b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira            showCopyDialog(currentChip.getEntry().getDestination());
1864b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        }
1865b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1866b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1867b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    private void showCopyDialog(final String address) {
1868b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = address;
1869b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setTitle(address);
1870b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setContentView(mCopyViewRes);
1871b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCancelable(true);
1872b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
1873b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this);
1874b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.setOnDismissListener(this);
1875b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.show();
1876b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1877b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1878b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1879b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
1880b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1881b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1882b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1883b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1884b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1885b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onShowPress(MotionEvent e) {
1886b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1887b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1888b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1889b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1890b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
1891b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Do nothing.
1892b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        return false;
1893b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1894b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1895b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1896b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onDismiss(DialogInterface dialog) {
1897b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyAddress = null;
1898b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
1899b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira
1900b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    @Override
1901b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    public void onClick(View v) {
1902b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        // Copy this to the clipboard.
1903b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
1904b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira                Context.CLIPBOARD_SERVICE);
1905b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
1906b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira        mCopyDialog.dismiss();
1907b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira    }
190877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira}
1909