RecipientEditTextView.java revision 80f4abfb682426384e88fb1dddc682be1c8a6c7f
19159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira/*
29159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Copyright (C) 2011 The Android Open Source Project
39159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *
49159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
59159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * you may not use this file except in compliance with the License.
69159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * You may obtain a copy of the License at
79159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *
89159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
99159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *
109159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Unless required by applicable law or agreed to in writing, software
119159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
129159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * See the License for the specific language governing permissions and
149159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * limitations under the License.
159159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira */
169159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
179159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereirapackage com.android.ex.chips;
189159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
191d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.app.Dialog;
201d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.ClipData;
2120c9d620e79ae28994856541761a951074551518Mindy Pereiraimport android.content.ClipDescription;
221d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.ClipboardManager;
239159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.content.Context;
241d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.DialogInterface;
251d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.DialogInterface.OnDismissListener;
2652c441e2c03e0f48572348953b985a4bf989c057Mindy Pereiraimport android.content.res.Resources;
2722faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereiraimport android.content.res.TypedArray;
28cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Bitmap;
292bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.BitmapFactory;
30cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Canvas;
312bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.Matrix;
32e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Phamimport android.graphics.Point;
33cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Rect;
342bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.RectF;
35cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
369159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.graphics.drawable.Drawable;
371852931de1e24e77cb708f4ba010eaa269426657Mindy Pereiraimport android.os.AsyncTask;
38007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.os.Handler;
39007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.os.Message;
40dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereiraimport android.os.Parcelable;
419159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.text.Editable;
4261b48ccefc655549802556947eb8cf3959c6ddadGilles Debunneimport android.text.InputType;
43cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Layout;
44cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Spannable;
45cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.SpannableString;
461852931de1e24e77cb708f4ba010eaa269426657Mindy Pereiraimport android.text.SpannableStringBuilder;
47cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Spanned;
48cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.TextPaint;
49cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.TextUtils;
50b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereiraimport android.text.TextWatcher;
51cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.method.QwertyKeyListener;
52cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.style.ImageSpan;
533bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereiraimport android.text.util.Rfc822Token;
543bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
559159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.util.AttributeSet;
56cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.util.Log;
57fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.ActionMode;
58aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadlerimport android.view.ActionMode.Callback;
59e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Phamimport android.view.DragEvent;
601d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.view.GestureDetector;
61cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.KeyEvent;
62750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereiraimport android.view.LayoutInflater;
63fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.Menu;
64fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.MenuItem;
65cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.MotionEvent;
66cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.View;
6761b48ccefc655549802556947eb8cf3959c6ddadGilles Debunneimport android.view.View.OnClickListener;
68c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereiraimport android.view.ViewParent;
69cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.AdapterView;
70cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
7180f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylorimport android.widget.Button;
7295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereiraimport android.widget.ListAdapter;
73cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.ListPopupWindow;
74007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.widget.ListView;
759159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.widget.MultiAutoCompleteTextView;
76ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereiraimport android.widget.PopupWindow;
77c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereiraimport android.widget.ScrollView;
78750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereiraimport android.widget.TextView;
797537f840506bcb642bed9dc1c2bdcf6d31c6b2a7Daisuke Miyakawa
80aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadlerimport java.util.ArrayList;
81e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereiraimport java.util.Arrays;
827537f840506bcb642bed9dc1c2bdcf6d31c6b2a7Daisuke Miyakawaimport java.util.Collection;
83e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereiraimport java.util.Collections;
84e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereiraimport java.util.Comparator;
851852931de1e24e77cb708f4ba010eaa269426657Mindy Pereiraimport java.util.HashMap;
86cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport java.util.HashSet;
87cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport java.util.Set;
88cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
899159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira/**
909159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
919159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * that use the new Chips UI for addressing a message to recipients.
929159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira */
9395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
941d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
95ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
96ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        PopupWindow.OnDismissListener {
97cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
98aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
99aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
100aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
101aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
102aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
103aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
104cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private static final String TAG = "RecipientEditTextView";
105cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
106aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
107aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
108aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final long DISMISS_DELAY = 300;
109aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
11012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
111f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
112f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ static final int CHIP_LIMIT = 2;
113f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
114f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private static final int MAX_CHIPS_PARSED = 50;
11512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
116aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static int sSelectedTextColor = -1;
117aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
118aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Resources for displaying chips.
119cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Drawable mChipBackground = null;
120cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
121cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Drawable mChipDelete = null;
122cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
123aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Drawable mInvalidChipBackground;
124aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
125aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Drawable mChipBackgroundPressed;
126aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
127aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private float mChipHeight;
128aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
129aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private float mChipFontSize;
130aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
131cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private int mChipPadding;
132cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
133cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Tokenizer mTokenizer;
134cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
135aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Validator mValidator;
136cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
137cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private RecipientChip mSelectedChip;
138cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
139cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private int mAlternatesLayout;
140cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1412bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    private Bitmap mDefaultContactPhoto;
1422bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
14312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private ImageSpan mMoreChip;
14412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
145750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereira    private TextView mMoreItem;
14612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
147ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira    private final ArrayList<String> mPendingChips = new ArrayList<String>();
148f97eb41a7946f2c3013ac74f2451b78070531125Mindy Pereira
149007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira    private Handler mHandler;
150007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira
15102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    private int mPendingChipsCount = 0;
15202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
153f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private boolean mNoChips = false;
154f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
15595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private ListPopupWindow mAlternatesPopup;
15695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
15701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    private ListPopupWindow mAddressPopup;
15801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
1591852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private ArrayList<RecipientChip> mTemporaryRecipients;
1601852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
1613bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    private ArrayList<RecipientChip> mRemovedSpans;
1623bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
163076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    private boolean mShouldShrink = true;
164076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira
1651d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    // Chip copy fields.
1661d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private GestureDetector mGestureDetector;
1671d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
1681d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private Dialog mCopyDialog;
1691d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
1701d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private String mCopyAddress;
1711d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
17295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
173aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
174aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler     * selected chip.
17595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
17695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private OnItemClickListener mAlternatesListener;
17795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
178c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private int mCheckedItem;
179aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
18079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private TextWatcher mTextWatcher;
18179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
182aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Obtain the enclosing scroll view, if it exists, so that the view can be
183aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // scrolled to show the last line of chips content.
184c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private ScrollView mScrollView;
185c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
186aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private boolean mTriedGettingScrollView;
187c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
188e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private boolean mDragEnabled = false;
189e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
19079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
19179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
19279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void run() {
19379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (mTextWatcher == null) {
19479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                mTextWatcher = new RecipientTextWatcher();
19579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                addTextChangedListener(mTextWatcher);
19679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
19779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
19879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    };
19979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
2001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
2011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
2022cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
2032cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
2042cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        @Override
2052cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        public void run() {
2062cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            handlePendingChips();
2072cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
2082cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
2092cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    };
2102cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
211d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    private Runnable mDelayedShrink = new Runnable() {
212d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
213d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        @Override
214d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        public void run() {
215d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            shrink();
216d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        }
217d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
218d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    };
219d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
2209159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2219159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira        super(context, attrs);
22222faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        setChipDimensions(context, attrs);
223e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        if (sSelectedTextColor == -1) {
224e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
225e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        }
22695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
227ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        mAlternatesPopup.setOnDismissListener(this);
22801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
229ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        mAddressPopup.setOnDismissListener(this);
230bfedc1e199e57dcda494389fdca0750e1f165135Mindy Pereira        mCopyDialog = new Dialog(context);
23195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
23295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            @Override
23395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
23495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                    long rowId) {
23521cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
236ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira                setEnabled(true);
23795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
23895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        .getRecipientEntry(position));
23995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
24021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                delayed.obj = mAlternatesPopup;
24195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
24295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                clearComposingText();
24395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
24495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        };
245e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
246b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira        setOnItemClickListener(this);
247fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        setCustomSelectionActionModeCallback(this);
248007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira        mHandler = new Handler() {
249007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            @Override
250007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            public void handleMessage(Message msg) {
251007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                if (msg.what == DISMISS) {
25295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
253007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                    return;
254007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                }
255007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                super.handleMessage(msg);
256007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            }
257007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira        };
2584e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        mTextWatcher = new RecipientTextWatcher();
2594e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        addTextChangedListener(mTextWatcher);
2601d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mGestureDetector = new GestureDetector(context, this);
261e33555f13a9b05d835cb860e2c30ef40af3c8502Erik    }
2627afe160db4a48f66c964ced89e29e0b63b23c7c1Mindy Pereira
263aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ RecipientChip getLastChip() {
264aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        RecipientChip last = null;
265aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
266aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (chips != null && chips.length > 0) {
267aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            last = chips[chips.length - 1];
268e33555f13a9b05d835cb860e2c30ef40af3c8502Erik        }
269aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        return last;
270fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    }
271fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira
272fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    @Override
273fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    public void onSelectionChanged(int start, int end) {
274fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // When selection changes, see if it is inside the chips area.
275fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // If so, move the cursor back after the chips again.
276aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        RecipientChip last = getLastChip();
277f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (last != null && start < getSpannable().getSpanEnd(last)) {
278aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            // Grab the last chip and set the cursor to after it.
279aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
28005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
28105dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        super.onSelectionChanged(start, end);
28205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    }
28305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira
284dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    @Override
285dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    public void onRestoreInstanceState(Parcelable state) {
286dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        if (!TextUtils.isEmpty(getText())) {
287dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            super.onRestoreInstanceState(null);
288dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        } else {
289dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            super.onRestoreInstanceState(state);
290dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        }
291dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    }
292dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira
293aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    @Override
294daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira    public Parcelable onSaveInstanceState() {
295daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
296daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        clearSelectedChip();
297daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        return super.onSaveInstanceState();
298daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira    }
299daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira
30002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    /**
30102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
30202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
30302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * not already editable. Commas are excluded as they are added automatically
30402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * by the view.
30502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     */
30602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    @Override
30702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    public void append(CharSequence text, int start, int end) {
308e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        // We don't care about watching text changes while appending.
309e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        if (mTextWatcher != null) {
310e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            removeTextChangedListener(mTextWatcher);
311e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        }
31202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        super.append(text, start, end);
31302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
31446c16479b9d2515b567430c589251fd193f6edb7Mindy Pereira            final String displayString = (String) text;
31564af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
31602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
31702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
31802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                mPendingChipsCount++;
319f97eb41a7946f2c3013ac74f2451b78070531125Mindy Pereira                mPendingChips.add((String)text);
32002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira            }
32102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        }
3222cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        // Put a message on the queue to make sure we ALWAYS handle pending chips.
3232cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        if (mPendingChipsCount > 0) {
3242cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            postHandlePendingChips();
3252cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
3264e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        mHandler.post(mAddTextWatcher);
32702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    }
32802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
32905dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    @Override
33005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
331d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
33205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        if (!hasFocus) {
33312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            shrink();
334fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        } else {
33512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            expand();
336fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        }
3379159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
3389159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
339d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    @Override
340d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    public void performValidation() {
341d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira        // Do nothing. Chips handles its own validation.
342d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    }
343d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira
34412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private void shrink() {
3455c125afa54288595524c85182031421cbad08ac3Mindy Pereira        if (mTokenizer == null) {
3465c125afa54288595524c85182031421cbad08ac3Mindy Pereira            return;
3475c125afa54288595524c85182031421cbad08ac3Mindy Pereira        }
34860148c51f518aec3e01f471c1e2eb28bea4e4112Mindy Pereira        if (mSelectedChip != null
34960148c51f518aec3e01f471c1e2eb28bea4e4112Mindy Pereira                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
35012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            clearSelectedChip();
35112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        } else {
352d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            if (getWidth() <= 0) {
353d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
354d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // and there is no reason to attempt to commit chips yet.
355d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // This focus lost must be the result of an orientation change
356d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // or an initial rendering.
357d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // Re-post the shrink for later.
358d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
359d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                mHandler.post(mDelayedShrink);
360d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                return;
361d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            }
362e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            // Reset any pending chips as they would have been handled
363e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            // when the field lost focus.
364e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            if (mPendingChipsCount > 0) {
3652cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                postHandlePendingChips();
366e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            } else {
367e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                Editable editable = getText();
368e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                int end = getSelectionEnd();
369e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
370e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
371e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                if ((chips == null || chips.length == 0)) {
372d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    Editable text = getText();
373d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
374d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    // This token was already tokenized, so skip past the ending token.
375448e90b97a4df8102d6e1d2039274d9ea188dff9Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
376d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                        whatEnd++;
377d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    }
378e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    // In the middle of chip; treat this as an edit
379e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    // and commit the whole token.
380d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    int selEnd = getSelectionEnd();
3816337231a660b717cd6f5c40d524f4aabfcc865b0Mindy Pereira                    if (whatEnd != selEnd) {
382e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                        handleEdit(start, whatEnd);
383e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    } else {
384e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                        commitChip(start, end, editable);
385e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    }
386e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                }
387a74f40dc1073117349d1d39b7c8396a39d24f57fMindy Pereira            }
388e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            mHandler.post(mAddTextWatcher);
38912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
3903bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        createMoreChip();
39112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
39212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
39312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private void expand() {
39412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        removeMoreChip();
39512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        setCursorVisible(true);
39612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        Editable text = getText();
39712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
3981852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
3991852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // has expanded the field.
4001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
4011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            new RecipientReplacementTask().execute();
4021852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            mTemporaryRecipients = null;
4031852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
40412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
40512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
4062bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
4076e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira        paint.setTextSize(mChipFontSize);
40802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
40902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
41002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        }
41102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
41202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                TextUtils.TruncateAt.END);
4132bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
414cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
4156e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
4162bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
4172bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
4182bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        // on the sides.
4196e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira        int height = (int) mChipHeight;
4202bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        int deleteWidth = height;
4216ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        float[] widths = new float[1];
4226ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        paint.getTextWidths(" ", widths);
423aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
4246ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira                calculateAvailableWidth(true) - deleteWidth - widths[0]);
4252bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
4262bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4272bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        // tap a chip without difficulty.
4282bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4292bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira                ellipsizedText.length()))
4302bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira                + (mChipPadding * 2) + deleteWidth);
431cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
4322bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        // Create the background of the chip.
4332bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
4342bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
4352bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        if (mChipBackgroundPressed != null) {
4362bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            mChipBackgroundPressed.setBounds(0, 0, width, height);
4372bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            mChipBackgroundPressed.draw(canvas);
438e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira            paint.setColor(sSelectedTextColor);
4391d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira            // Vertically center the text in the chip.
4401d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
4411d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira                    getTextYOffset((String) ellipsizedText, paint, height), paint);
4422bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            // Make the delete a square.
4430265967fb4b870fc02b3b5690a7ac9681542c346Mindy Pereira            Rect backgroundPadding = new Rect();
4440265967fb4b870fc02b3b5690a7ac9681542c346Mindy Pereira            mChipBackgroundPressed.getPadding(backgroundPadding);
4450265967fb4b870fc02b3b5690a7ac9681542c346Mindy Pereira            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
4460265967fb4b870fc02b3b5690a7ac9681542c346Mindy Pereira                    0 + backgroundPadding.top,
4470265967fb4b870fc02b3b5690a7ac9681542c346Mindy Pereira                    width - backgroundPadding.right,
4480265967fb4b870fc02b3b5690a7ac9681542c346Mindy Pereira                    height - backgroundPadding.bottom);
4492bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            mChipDelete.draw(canvas);
4502bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        } else {
4512bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
4522bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
4532bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        return tmpBitmap;
4542bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
455cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
456342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira
4576e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
458cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
459cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
460cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // on the sides.
4616e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira        int height = (int) mChipHeight;
4622bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        int iconWidth = height;
4636ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        float[] widths = new float[1];
4646ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        paint.getTextWidths(" ", widths);
465aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
4666ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira                calculateAvailableWidth(false) - iconWidth - widths[0]);
4679d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
4689d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira        // tap a chip without difficulty.
4692bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
4702bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira                ellipsizedText.length()))
4712bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira                + (mChipPadding * 2) + iconWidth);
472cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
473cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Create the background of the chip.
474cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
475cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
476342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira        Drawable background = getChipBackground(contact);
477342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira        if (background != null) {
478342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira            background.setBounds(0, 0, width, height);
479342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira            background.draw(canvas);
4802bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
481364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            // Don't draw photos for recipients that have been typed in.
48201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
483364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                byte[] photoBytes = contact.getPhotoBytes();
4846b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                // There may not be a photo yet if anything but the first contact address
4856b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                // was selected.
4866b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
4876b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                    // TODO: cache this in the recipient entry?
4886b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                    ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact
4896b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                            .getPhotoThumbnailUri());
4906b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                    photoBytes = contact.getPhotoBytes();
4916b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira                }
4926b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira
493364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                Bitmap photo;
494364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                if (photoBytes != null) {
495364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
496364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                } else {
497364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                    // TODO: can the scaled down default photo be cached?
498364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                    photo = mDefaultContactPhoto;
499364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                }
500364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                // Draw the photo on the left side.
50197cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                if (photo != null) {
50297cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
50397cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                    Rect backgroundPadding = new Rect();
50497cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                    mChipBackground.getPadding(backgroundPadding);
50597cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
50697cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                            0 + backgroundPadding.top,
50797cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                            width - backgroundPadding.right,
50897cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                            height - backgroundPadding.bottom);
50997cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                    Matrix matrix = new Matrix();
51097cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
51197cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                    canvas.drawBitmap(photo, matrix, paint);
51297cb25912dab282cf732757f68b0405ed005f00bMindy Pereira                }
513cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            } else {
514364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                // Don't leave any space for the icon. It isn't being drawn.
515364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira                iconWidth = 0;
516cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
517ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira            paint.setColor(getContext().getResources().getColor(android.R.color.black));
5181d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira            // Vertically center the text in the chip.
5192bc578406c81a6be7fae3b1216b8b175fd7446e1Mindy Pereira            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
5201d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira                    getTextYOffset((String)ellipsizedText, paint, height), paint);
521cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        } else {
5222bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
5232bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
5242bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        return tmpBitmap;
5252bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
526cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
527aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /**
528aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira     * Get the background drawable for a RecipientChip.
529aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira     */
530aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
531aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ Drawable getChipBackground(RecipientEntry contact) {
532aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
533aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                mChipBackground : mInvalidChipBackground;
534aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    }
535aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
5361d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira    private float getTextYOffset(String text, TextPaint paint, int height) {
5371d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        Rect bounds = new Rect();
538e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        paint.getTextBounds(text, 0, text.length(), bounds);
5391d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        int textHeight = bounds.bottom - bounds.top  - (int)paint.descent();
5401d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        return height - ((height - textHeight) / 2);
5411d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira    }
5421d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira
543aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
5442bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            throws NullPointerException {
5452bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        if (mChipBackground == null) {
5462bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            throw new NullPointerException(
5472bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
548cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
5492bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        Layout layout = getLayout();
55002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
5512bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        TextPaint paint = getPaint();
5522bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        float defaultSize = paint.getTextSize();
553e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        int defaultColor = paint.getColor();
5542bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
5552bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        Bitmap tmpBitmap;
5562bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        if (pressed) {
5576e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira            tmpBitmap = createSelectedChip(contact, paint, layout);
558cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
5592bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        } else {
560342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira            tmpBitmap = createUnselectedChip(contact, paint, layout);
5612bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
562cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
563cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
564cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
5652bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
566e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
567cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Return text to the original size.
568cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        paint.setTextSize(defaultSize);
569e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        paint.setColor(defaultColor);
570cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return recipientChip;
5719159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
5729159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
573a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
574342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
575342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * 1) which line the chip appears on
57621625f86828b6bbfc5e87796564eaca5127155feMindy Pereira     * 2) the height of a chip
57721625f86828b6bbfc5e87796564eaca5127155feMindy Pereira     * 3) padding built into the edit text view
578a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
57981fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira    private int calculateOffsetFromBottom(int line) {
58021625f86828b6bbfc5e87796564eaca5127155feMindy Pereira        // Line offsets start at zero.
581c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        int actualLine = getLineCount() - (line + 1);
5821d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
5831d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira                + getDropDownVerticalOffset();
584f621a601e1f966c89b7aadbcca384021e14d668dMindy Pereira    }
585f621a601e1f966c89b7aadbcca384021e14d668dMindy Pereira
586a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
587a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
588a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
589a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * that will be added to the chip.
590a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
591cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private float calculateAvailableWidth(boolean pressed) {
5922bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
5939159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
5949159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
59552c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira
59622faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira    private void setChipDimensions(Context context, AttributeSet attrs) {
5975da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecipientEditTextView, 0,
5985da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                0);
59952c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira        Resources r = getContext().getResources();
60022faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        mChipBackground = a.getDrawable(R.styleable.RecipientEditTextView_chipBackground);
60122faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        if (mChipBackground == null) {
60222faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira            mChipBackground = r.getDrawable(R.drawable.chip_background);
60322faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        }
6045da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipBackgroundPressed = a
6055da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                .getDrawable(R.styleable.RecipientEditTextView_chipBackgroundPressed);
6065da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipBackgroundPressed == null) {
6075da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipBackgroundPressed = r.getDrawable(R.drawable.chip_background_selected);
6085da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
6095da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipDelete = a.getDrawable(R.styleable.RecipientEditTextView_chipDelete);
6105da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipDelete == null) {
6115da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipDelete = r.getDrawable(R.drawable.chip_delete);
6125da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
6135da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipPadding = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipPadding, -1);
6145da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipPadding == -1) {
6155da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
6165da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
6175da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mAlternatesLayout = a.getResourceId(R.styleable.RecipientEditTextView_chipAlternatesLayout,
6185da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                -1);
6195da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mAlternatesLayout == -1) {
6205da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mAlternatesLayout = R.layout.chips_alternate_item;
6215da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
6225da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
6235da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
6245da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
62552c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.more_item, null);
6265da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
6275da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipHeight = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipHeight, -1);
6285da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipHeight == -1) {
6295da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipHeight = r.getDimension(R.dimen.chip_height);
6305da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
6315da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipFontSize = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipFontSize, -1);
6325da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipFontSize == -1) {
6335da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipFontSize = r.getDimension(R.dimen.chip_text_size);
6345da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
6355da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mInvalidChipBackground = a
6365da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                .getDrawable(R.styleable.RecipientEditTextView_invalidChipBackground);
6375da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mInvalidChipBackground == null) {
6385da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
6395da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
64052c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira    }
64152c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira
642d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
643d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
644d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        mMoreItem = moreItem;
645d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    }
646d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
64797cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
64897cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
64997cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
65097cb25912dab282cf732757f68b0405ed005f00bMindy Pereira        mChipBackground = chipBackground;
65197cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    }
65297cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
65397cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
65497cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    /* package */ void setChipHeight(int height) {
65597cb25912dab282cf732757f68b0405ed005f00bMindy Pereira        mChipHeight = height;
65697cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    }
65797cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
658076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    /**
659076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * Set whether to shrink the recipients field such that at most
660076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * one line of recipients chips are shown when the field loses
661076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * focus. By default, the number of displayed recipients will be
662076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
663076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * @param shrink
664076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     */
665076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
666076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        mShouldShrink = shrink;
667076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    }
668076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira
669cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
67002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
67102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
672db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (width != 0 && height != 0) {
673db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            if (mPendingChipsCount > 0) {
674db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                postHandlePendingChips();
675db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            } else {
676db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                checkChipWidths();
677db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            }
6788005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira        }
6791852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // Try to find the scroll view parent, if it exists.
680aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
681c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            ViewParent parent = getParent();
682c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
683c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                parent = parent.getParent();
684c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            }
685c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            if (parent != null) {
686c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                mScrollView = (ScrollView) parent;
687c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            }
688aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            mTriedGettingScrollView = true;
689c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        }
69002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    }
69102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
6922cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    private void postHandlePendingChips() {
6932cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
6942cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        mHandler.post(mHandlePendingChips);
6952cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    }
6962cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
697db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira    private void checkChipWidths() {
698db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        // Check the widths of the associated chips.
699db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
700db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (chips != null) {
701db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            Rect bounds;
702db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            for (RecipientChip chip : chips) {
703db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                bounds = chip.getDrawable().getBounds();
704db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
705db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                    // Need to redraw that chip.
706db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                    replaceChip(chip, chip.getEntry());
707db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                }
708db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            }
7092cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
710db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira    }
711db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira
712f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
713f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ void handlePendingChips() {
714f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (getViewWidth() <= 0) {
7158005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // The widget has not been sized yet.
7168005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // This will be called as a result of onSizeChanged
7178005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // at a later point.
7188005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            return;
7198005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira        }
720db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (mPendingChipsCount <= 0) {
721db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            return;
722db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        }
723db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira
7242cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        synchronized (mPendingChips) {
7252cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            Editable editable = getText();
7262cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            // Tokenize!
727f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
728f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                for (int i = 0; i < mPendingChips.size(); i++) {
729f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    String current = mPendingChips.get(i);
730f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    int tokenStart = editable.toString().indexOf(current);
731f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    int tokenEnd = tokenStart + current.length();
732f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    if (tokenStart >= 0) {
733f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        // When we have a valid token, include it with the token
734f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        // to the left.
735f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        if (tokenEnd < editable.length() - 2
736f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                                && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
737f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                            tokenEnd++;
738f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        }
739f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        createReplacementChip(tokenStart, tokenEnd, editable);
7402cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    }
741f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    mPendingChipsCount--;
7423bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                }
743f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                sanitizeEnd();
744f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            } else {
745f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                mNoChips = true;
7463bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
747f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
748dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
7492cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
7502cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
7512cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    new RecipientReplacementTask().execute();
7522cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    mTemporaryRecipients = null;
7532cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                } else {
7542cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    // Create the "more" chip
7552cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
7562cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
7572cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
7581852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
7592cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    createMoreChip();
7602cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                }
7612cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            } else {
7622cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                // There are too many recipients to look up, so just fall back
763dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                // to showing addresses for all of them.
7642cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                mTemporaryRecipients = null;
7651852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                createMoreChip();
7661852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
7672cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            mPendingChipsCount = 0;
7682cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            mPendingChips.clear();
7693bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
7703bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
7713bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
772f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
773f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ int getViewWidth() {
774f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return getWidth();
775f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
776f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
7773bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    /**
7783bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * Remove any characters after the last valid chip.
7793bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     */
780aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
781aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ void sanitizeEnd() {
7823c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        // Don't sanitize while we are waiting for pending chips to complete.
7833c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        if (mPendingChipsCount > 0) {
7843c42baf5b81810bce77e776963f20c960865e85bMindy Pereira            return;
7853c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        }
7863bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
787aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
7883bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        if (chips != null && chips.length > 0) {
7893bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            int end;
7903bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            ImageSpan lastSpan;
791dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            mMoreChip = getMoreChip();
7923bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            if (mMoreChip != null) {
7933bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                lastSpan = mMoreChip;
7943bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            } else {
795aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                lastSpan = getLastChip();
7963bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
7973bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            end = getSpannable().getSpanEnd(lastSpan);
7983bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            Editable editable = getText();
7993bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            int length = editable.length();
8003bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            if (length > end) {
8013bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                // See what characters occur after that and eliminate them.
8023bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
8033bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
8043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                            + editable);
8053bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                }
8063bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                editable.delete(end + 1, length);
8073bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
8083bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
8093bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
8103bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
8113bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    /**
8123bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
8133bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
8143bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     */
8153bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
81632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
81732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // There is already a chip present at this location.
81832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // Don't recreate it.
81932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            return;
82032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        }
8217ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
82232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
8233bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        if (commitCharIndex == token.length() - 1) {
8243bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            token = token.substring(0, token.length() - 1);
8253bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
8263bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
827ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        if (entry != null) {
828aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            String destText = createAddressText(entry);
829ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            // Always leave a blank space at the end of a chip.
83062397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira            int textLength = destText.length() - 1;
831ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            SpannableString chipText = new SpannableString(destText);
832ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            int end = getSelectionEnd();
83332aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira            int start = mTokenizer != null ? mTokenizer.findTokenStart(getText(), end) : 0;
834ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            RecipientChip chip = null;
835ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            try {
836f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (!mNoChips) {
837f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    chip = constructChipSpan(entry, start, false);
838f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
839f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
840ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            } catch (NullPointerException e) {
841ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                Log.e(TAG, e.getMessage(), e);
842ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
843ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            editable.replace(tokenStart, tokenEnd, chipText);
844ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            // Add this chip to the list of entries "to replace"
845ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            if (chip != null) {
846dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                if (mTemporaryRecipients == null) {
847dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                    mTemporaryRecipients = new ArrayList<RecipientChip>();
848dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                }
849e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                chip.setOriginalText(chipText.toString());
850ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                mTemporaryRecipients.add(chip);
851ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
8521852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
8533bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
8543bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
8553bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    private RecipientEntry createTokenizedEntry(String token) {
856ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        if (TextUtils.isEmpty(token)) {
857ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            return null;
858ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        }
8593bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
86001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        String display = null;
8614e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        if (isValid(token) && tokens != null && tokens.length > 0) {
862ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira            // If we can get a name from tokenizing, then generate an entry from
863ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira            // this.
86401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            display = tokens[0].getName();
86501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (!TextUtils.isEmpty(display)) {
86601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                return RecipientEntry.constructGeneratedEntry(display, token);
867401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira            } else {
868401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                display = tokens[0].getAddress();
869401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                if (!TextUtils.isEmpty(display)) {
870401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                    return RecipientEntry.constructFakeEntry(display);
871401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                }
87201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
87301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        }
8744e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        // Unable to validate the token or to create a valid token from it.
8754e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        // Just create a chip the user can edit.
876dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira        String validatedToken = null;
8774e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        if (mValidator != null && !mValidator.isValid(token)) {
8784e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            // Try fixing up the entry using the validator.
879dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira            validatedToken = mValidator.fixText(token).toString();
880dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
881dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                if (validatedToken.contains(token)) {
882dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // protect against the case of a validator with a null domain,
883dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // which doesn't add a domain to the token
884dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
885dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    if (tokenized.length > 0) {
886dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                        validatedToken = tokenized[0].getAddress();
887dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    }
888dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                } else {
889dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // We ran into a case where the token was invalid and removed
890dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // by the validator. In this case, just use the original token
891dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // and let the user sort out the error chip.
892dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    validatedToken = null;
893184d3773dc096c7a6a83f7aeda042fa8346c2024Erik                }
894ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
8954e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        }
896ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
897dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira        return RecipientEntry
898dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
89901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
90001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
9014e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira    private boolean isValid(String text) {
9024e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
9034e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira    }
9044e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira
90501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    private String tokenizeAddress(String destination) {
90601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
90701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        if (tokens != null && tokens.length > 0) {
90801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            return tokens[0].getAddress();
9093bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
91001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        return destination;
9113bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
9123bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
91302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    @Override
914cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
915cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        mTokenizer = tokenizer;
916cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        super.setTokenizer(mTokenizer);
917cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
918cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
919a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    @Override
920a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    public void setValidator(Validator validator) {
921a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira        mValidator = validator;
922a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira        super.setValidator(validator);
923a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    }
924a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira
925a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
926a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
927a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * we override onItemClickListener so we can get all the associated
928a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * contact information including display text, address, and id.
929a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
930cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
931cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    protected void replaceText(CharSequence text) {
932cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return;
933cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
934cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
935a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
936a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
937a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
938cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
9394c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
9404c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK) {
9414c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira            clearSelectedChip();
9424c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira        }
9434c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira        return super.onKeyPreIme(keyCode, event);
9444c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    }
9454c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira
946a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
947a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Monitor key presses in this view to see if the user types
948a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
949a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the user has entered text that has contact matches and types
950a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
951a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the user has entered text that has no contact matches and types
952a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * a commit key, then create a chip from the text they have entered.
953a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
9544c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    @Override
955cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
956cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        switch (keyCode) {
957cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            case KeyEvent.KEYCODE_ENTER:
958cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            case KeyEvent.KEYCODE_DPAD_CENTER:
959cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                if (event.hasNoModifiers()) {
96005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira                    if (commitDefault()) {
961cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                        return true;
962cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    }
9630f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    if (mSelectedChip != null) {
9640f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        clearSelectedChip();
9650f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        return true;
966ffc42764b959ccd3350dee38bb7cba128995b181Mindy Pereira                    } else if (focusNext()) {
967ffc42764b959ccd3350dee38bb7cba128995b181Mindy Pereira                        return true;
9680f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    }
969cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                }
9704c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira                break;
9710f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            case KeyEvent.KEYCODE_TAB:
9720f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                if (event.hasNoModifiers()) {
9730f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    if (mSelectedChip != null) {
9740f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        clearSelectedChip();
9750f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    } else {
9760f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        commitDefault();
9770f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    }
9780f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    if (focusNext()) {
9790f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        return true;
9800f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    }
9810f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                }
982cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
983cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return super.onKeyUp(keyCode, event);
984cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
985cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
9860f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira    private boolean focusNext() {
9870f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
9880f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        if (next != null) {
9890f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            next.requestFocus();
9900f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            return true;
9910f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        }
9920f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        return false;
9930f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira    }
9940f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira
995342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira    /**
996342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
997342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
998342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
999342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * should search for a token to turn into a chip.
1000342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * @return If a chip was created from a real contact.
1001342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     */
1002a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    private boolean commitDefault() {
100312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        Editable editable = getText();
1004342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira        int end = getSelectionEnd();
100512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
10062b4ffc53a3b51631cb6aabf535986a9344ee6cbbMindy Pereira
100779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        if (shouldCreateChip(start, end)) {
100879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
100979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // In the middle of chip; treat this as an edit
101079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // and commit the whole token.
101179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (whatEnd != getSelectionEnd()) {
101279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                handleEdit(start, whatEnd);
101379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                return true;
101412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            }
101579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            return commitChip(start, end , editable);
101679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
101779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        return false;
101879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
101979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
102079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private void commitByCharacter() {
102179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        Editable editable = getText();
102279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        int end = getSelectionEnd();
102379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
102479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        if (shouldCreateChip(start, end)) {
102579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            commitChip(start, end, editable);
102612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
1027ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira        setSelection(getText().length());
102879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
102912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
103079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
103120c9d620e79ae28994856541761a951074551518Mindy Pereira        ListAdapter adapter = getAdapter();
103220c9d620e79ae28994856541761a951074551518Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
103320c9d620e79ae28994856541761a951074551518Mindy Pereira                && end == getSelectionEnd()) {
103479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // choose the first entry.
103579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            submitItemAtPosition(0);
103679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            dismissDropDown();
103779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            return true;
103879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        } else {
103979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
104020c9d620e79ae28994856541761a951074551518Mindy Pereira            if (editable.length() > tokenEnd + 1) {
104120c9d620e79ae28994856541761a951074551518Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
104220c9d620e79ae28994856541761a951074551518Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
104320c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenEnd++;
104420c9d620e79ae28994856541761a951074551518Mindy Pereira                }
1045d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            }
104662397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
104779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            clearComposingText();
104879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
104901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1050ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                if (entry != null) {
1051ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1052ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                    CharSequence chipText = createChip(entry, false);
1053ee6d83fe1da297b2f9af0fb221be376fdc816830Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
1054f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira                        editable.replace(start, end, chipText);
1055f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira                    }
1056ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                }
105720c9d620e79ae28994856541761a951074551518Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
105820c9d620e79ae28994856541761a951074551518Mindy Pereira                // just committed.
105920c9d620e79ae28994856541761a951074551518Mindy Pereira                // For paste, it may not be as there are possibly multiple
106020c9d620e79ae28994856541761a951074551518Mindy Pereira                // tokens being added.
106120c9d620e79ae28994856541761a951074551518Mindy Pereira                if (end == getSelectionEnd()) {
106220c9d620e79ae28994856541761a951074551518Mindy Pereira                    dismissDropDown();
106320c9d620e79ae28994856541761a951074551518Mindy Pereira                }
10645df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                sanitizeBetween();
106512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                return true;
106605dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            }
106705dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
106812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
106905dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    }
107005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira
107101162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
107201162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    /* package */ void sanitizeBetween() {
10733c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        // Don't sanitize while we are waiting for content to chipify.
10743c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        if (mPendingChipsCount > 0) {
10753c42baf5b81810bce77e776963f20c960865e85bMindy Pereira            return;
10763c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        }
10775df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        // Find the last chip.
107801162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira        RecipientChip[] recips = getSortedRecipients();
10795df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        if (recips != null && recips.length > 0) {
10805df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            RecipientChip last = recips[recips.length - 1];
10815df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            RecipientChip beforeLast = null;
10825df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            if (recips.length > 1) {
10835df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                beforeLast = recips[recips.length - 2];
10845df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
10855df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            int startLooking = 0;
10865df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            int end = getSpannable().getSpanStart(last);
10875df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            if (beforeLast != null) {
10885df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1089399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                Editable text = getText();
109001162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1091399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                    // There is nothing after this chip.
1092399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                    return;
1093399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                }
1094399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
10955df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                    startLooking++;
10965df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                }
10975df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
1098e13775cc6e9342e42db0b853cc42dbfda6d1365fMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking < end) {
10995df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                getText().delete(startLooking, end);
11005df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
11015df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        }
11025df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira    }
11035df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira
110479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private boolean shouldCreateChip(int start, int end) {
1105f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
110632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira    }
110732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira
110832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
1109f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (mNoChips) {
1110f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            return true;
1111f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
111232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
111332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        if ((chips == null || chips.length == 0)) {
111432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            return false;
111564af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira        }
111632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        return true;
111779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
111879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
111979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private void handleEdit(int start, int end) {
1120a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira        if (start == -1 || end == -1) {
1121a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            // This chip no longer exists in the field.
1122a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            dismissDropDown();
1123a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            return;
1124a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira        }
112579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        // This is in the middle of a chip, so select out the whole chip
112679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        // and commit it.
112779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        Editable editable = getText();
112879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        setSelection(end);
112979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        String text = getText().toString().substring(start, end);
1130fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira        if (!TextUtils.isEmpty(text)) {
1131fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
1132fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
1133fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira            CharSequence chipText = createChip(entry, false);
11345cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira            int selEnd = getSelectionEnd();
11355cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira            if (chipText != null && start > -1 && selEnd > -1) {
11365cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira                editable.replace(start, selEnd, chipText);
11372d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            }
1138fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira        }
1139ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira        dismissDropDown();
114064af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira    }
114164af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira
1142a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1143a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If there is a selected chip, delegate the key events
1144a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * to the selected chip.
1145a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1146cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1147cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
114895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
114995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
115095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                mAlternatesPopup.dismiss();
115195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
115295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            removeChip(mSelectedChip);
1153cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1154cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1155cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
1156cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            return true;
1157cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1158cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1159cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return super.onKeyDown(keyCode, event);
11609159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
11619159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
116201162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
116301162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    /* package */ Spannable getSpannable() {
1164e33555f13a9b05d835cb860e2c30ef40af3c8502Erik        return getText();
1165cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1166cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
116795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private int getChipStart(RecipientChip chip) {
116895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return getSpannable().getSpanStart(chip);
116995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
117095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
117195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private int getChipEnd(RecipientChip chip) {
117295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return getSpannable().getSpanEnd(chip);
117395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
117495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1175cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /**
1176cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1177cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * this subclass method filters on the range from
1178cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1179cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1180cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * and makes sure that the range is not already a Chip.
1181cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     */
1182cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1183cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
118420c9d620e79ae28994856541761a951074551518Mindy Pereira        if (enoughToFilter() && !isCompletedToken(text)) {
1185cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            int end = getSelectionEnd();
1186cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1187cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // If this is a RecipientChip, don't filter
1188cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // on its contents.
1189cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            Spannable span = getSpannable();
1190cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
1191cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (chips != null && chips.length > 0) {
1192cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                return;
1193cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1194cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1195cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        super.performFiltering(text, keyCode);
1196cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1197cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
119820c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
119920c9d620e79ae28994856541761a951074551518Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
120020c9d620e79ae28994856541761a951074551518Mindy Pereira        if (TextUtils.isEmpty(text)) {
120120c9d620e79ae28994856541761a951074551518Mindy Pereira            return false;
120220c9d620e79ae28994856541761a951074551518Mindy Pereira        }
120320c9d620e79ae28994856541761a951074551518Mindy Pereira        // Check to see if this is a completed token before filtering.
120420c9d620e79ae28994856541761a951074551518Mindy Pereira        int end = text.length();
120520c9d620e79ae28994856541761a951074551518Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
120620c9d620e79ae28994856541761a951074551518Mindy Pereira        String token = text.toString().substring(start, end).trim();
120720c9d620e79ae28994856541761a951074551518Mindy Pereira        if (!TextUtils.isEmpty(token)) {
120820c9d620e79ae28994856541761a951074551518Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
120920c9d620e79ae28994856541761a951074551518Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
121020c9d620e79ae28994856541761a951074551518Mindy Pereira        }
121120c9d620e79ae28994856541761a951074551518Mindy Pereira        return false;
121220c9d620e79ae28994856541761a951074551518Mindy Pereira    }
121320c9d620e79ae28994856541761a951074551518Mindy Pereira
1214cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private void clearSelectedChip() {
1215cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        if (mSelectedChip != null) {
121695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            unselectChip(mSelectedChip);
1217cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            mSelectedChip = null;
1218cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1219b86dcd5230ebcc57e5fc7a669c2304aca142dbf5Mindy Pereira        setCursorVisible(true);
1220cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1221cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1222a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1223a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
1224a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the view does not have focus, any tap on the view
1225a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * will just focus the view. If the view has focus, determine
1226a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
1227a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * is not selected, select it and clear any other selected chips.
1228a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If it isn't, then select that chip.
1229a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1230cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1231cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
123205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        if (!isFocused()) {
123305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            // Ignore any chip taps until this view is focused.
123405dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            return super.onTouchEvent(event);
123505dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
1236cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        boolean handled = super.onTouchEvent(event);
123705dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        int action = event.getAction();
1238cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        boolean chipWasSelected = false;
12392e905906f83fb1285498f09fce4db4a5878efbccMindy Pereira        if (mSelectedChip == null) {
12401d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira            mGestureDetector.onTouchEvent(event);
12411d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
1242bfedc1e199e57dcda494389fdca0750e1f165135Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1243cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            float x = event.getX();
1244cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            float y = event.getY();
1245cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            int offset = putOffsetInRange(getOffsetForPosition(x, y));
1246cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            RecipientChip currentChip = findChip(offset);
1247cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (currentChip != null) {
1248cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1249cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1250cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                        clearSelectedChip();
125195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1252cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    } else if (mSelectedChip == null) {
1253a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira                        setSelection(getText().length());
1254a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira                        commitDefault();
125595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1256cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    } else {
125795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1258cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    }
1259cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                }
1260cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                chipWasSelected = true;
1261c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                handled = true;
1262c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            } else if (mSelectedChip != null
1263c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
1264c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira                chipWasSelected = true;
1265cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1266cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1267cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1268cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            clearSelectedChip();
1269cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1270cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return handled;
1271cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1272cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1273c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private void scrollLineIntoView(int line) {
1274c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        if (mScrollView != null) {
127581fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
1276c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        }
1277c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    }
1278c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
127995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
128095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int width, Context context) {
128195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
128281fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
128395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Align the alternates popup with the left side of the View,
128495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // regardless of the position of the chip tapped.
128595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        alternatesPopup.setWidth(width);
1286ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        setEnabled(false);
12871852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        alternatesPopup.setAnchorView(this);
1288c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        alternatesPopup.setVerticalOffset(bottom);
128995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
129021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        alternatesPopup.setOnItemClickListener(mAlternatesListener);
129198b547f2eeb80259036e3f528636d7cbd823bf6dMindy Pereira        // Clear the checked item.
129298b547f2eeb80259036e3f528636d7cbd823bf6dMindy Pereira        mCheckedItem = -1;
129395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        alternatesPopup.show();
129495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        ListView listView = alternatesPopup.getListView();
129595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
129695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Checked item would be -1 if the adapter has not
129795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // loaded the view that should be checked yet. The
129895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // variable will be set correctly when onCheckedItemChanged
129995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // is called in a separate thread.
130095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (mCheckedItem != -1) {
130195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            listView.setItemChecked(mCheckedItem, true);
130295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mCheckedItem = -1;
130395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        }
130495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
130595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1306ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira    // Dismiss listener for alterns and single address popup.
1307ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira    @Override
1308ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira    public void onDismiss() {
1309ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        setEnabled(true);
1310ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira    }
1311ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira
131295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
131395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
131480f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor                mAlternatesLayout, BaseRecipientAdapter.QUERY_TYPE_PHONE, this);
131595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
131695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
131701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
131801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
131901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                .getEntry());
132001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
132101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
1322aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler    @Override
132395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    public void onCheckedItemChanged(int position) {
132495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
132595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
132695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            listView.setItemChecked(position, true);
132795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        }
132898b547f2eeb80259036e3f528636d7cbd823bf6dMindy Pereira        mCheckedItem = position;
132995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
133095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1331cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1332cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1333cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // what comes before the finger.
1334cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private int putOffsetInRange(int o) {
1335cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int offset = o;
1336cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Editable text = getText();
1337cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int length = text.length();
1338cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Remove whitespace from end to find "real end"
1339cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int realLength = length;
1340cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1341cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (text.charAt(i) == ' ') {
1342cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                realLength--;
1343cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            } else {
1344cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                break;
1345cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1346cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1347cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1348fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // If the offset is beyond or at the end of the text,
1349fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // leave it alone.
1350fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        if (offset >= realLength) {
1351cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            return offset;
1352cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1353fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        Editable editable = getText();
1354b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1355cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // Keep walking backward!
1356cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            offset--;
1357cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1358cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return offset;
1359cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1360cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1361fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    private int findText(Editable text, int offset) {
1362fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        if (text.charAt(offset) != ' ') {
1363fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira            return offset;
1364fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        }
1365fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        return -1;
1366fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    }
1367fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira
1368cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private RecipientChip findChip(int offset) {
1369b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
1370cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Find the chip that contains this offset.
1371cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        for (int i = 0; i < chips.length; i++) {
1372cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            RecipientChip chip = chips[i];
137395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int start = getChipStart(chip);
137495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int end = getChipEnd(chip);
137595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (offset >= start && offset <= end) {
1376cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                return chip;
1377cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1378cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1379cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return null;
1380cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1381cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
138201162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
1383aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Use this method to generate text to add to the list of addresses.
138432aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira    /* package */String createAddressText(RecipientEntry entry) {
13853ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        String display = entry.getDisplayName();
13863ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        String address = entry.getDestination();
13873ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
13883ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira            display = null;
13893ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        }
1390d8c15c328eaa109946f8f9093f3c2f2773d525ddMindy Pereira        if (address != null) {
1391d8c15c328eaa109946f8f9093f3c2f2773d525ddMindy Pereira            // Tokenize out the address in case the address already
1392d8c15c328eaa109946f8f9093f3c2f2773d525ddMindy Pereira            // contained the username as well.
1393ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1394ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1395ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                address = tokenized[0].getAddress();
1396ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira            }
1397d8c15c328eaa109946f8f9093f3c2f2773d525ddMindy Pereira        }
13983ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        Rfc822Token token = new Rfc822Token(display, address, null);
1399aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String trimmedDisplayText = token.toString().trim();
14003ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        int index = trimmedDisplayText.indexOf(",");
140132aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira        return mTokenizer != null && !TextUtils.isEmpty(trimmedDisplayText)
140232aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira                && index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
1403aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
1404aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    }
1405aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
1406aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
1407aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Use this method to generate text to display in a chip.
1408aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
1409aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String display = entry.getDisplayName();
1410aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String address = entry.getDestination();
1411aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
1412aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            display = null;
1413aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
1414aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (address != null) {
1415aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            // Tokenize out the address in case the address already
1416aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            // contained the username as well.
1417aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1418aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            if (tokenized != null && tokenized.length > 0) {
1419aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                address = tokenized[0].getAddress();
1420aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            }
1421aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
1422aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (!TextUtils.isEmpty(display)) {
1423aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return display;
1424aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
1425aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return address;
1426aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        } else {
1427aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return new Rfc822Token(display, address, null).toString();
1428aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
14293ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira    }
14303ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira
14310ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1432aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String displayText = createAddressText(entry);
14332d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
14342d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            return null;
14352d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        }
1436f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        SpannableString chipText = null;
1437cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Always leave a blank space at the end of a chip.
1438cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int end = getSelectionEnd();
1439cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1440f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int textLength = displayText.length()-1;
1441f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        chipText = new SpannableString(displayText);
1442f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (!mNoChips) {
1443f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            try {
1444f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                RecipientChip chip = constructChipSpan(entry, start, pressed);
1445f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                chipText.setSpan(chip, 0, textLength,
1446f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1447f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                chip.setOriginalText(chipText.toString());
1448f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            } catch (NullPointerException e) {
1449f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1450f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                return null;
1451f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            }
1452cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1453cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return chipText;
1454cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1455cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1456a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1457a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
1458a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * contact information of the selected item.
1459a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1460cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1461cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1462cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        submitItemAtPosition(position);
1463cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1464cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1465cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private void submitItemAtPosition(int position) {
14663bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        RecipientEntry entry = createValidatedEntry(
14673bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                (RecipientEntry)getAdapter().getItem(position));
1468f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik        if (entry == null) {
1469f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik            return;
1470f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik        }
1471cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        clearComposingText();
1472cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1473cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int end = getSelectionEnd();
1474cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1475cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1476cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Editable editable = getText();
1477cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
1478e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira        CharSequence chip = createChip(entry, false);
1479005b2e29742d7b9f3ecc505a50b11d91bd44c818Mindy Pereira        if (chip != null && start >= 0 && end >= 0) {
1480e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira            editable.replace(start, end, chip);
1481e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira        }
14825df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        sanitizeBetween();
1483cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1484cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
14853bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
14863bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        if (item == null) {
14873bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            return null;
14883bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
14893bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        final RecipientEntry entry;
14903bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // If the display name and the address are the same, or if this is a
14913bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
14923bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // recipient that is editable.
14933bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        String destination = item.getDestination();
1494b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        if (RecipientEntry.isCreatedRecipient(item.getContactId())
1495b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                && (TextUtils.isEmpty(item.getDisplayName())
1496b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                        || TextUtils.equals(item.getDisplayName(), destination)
1497b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                        || (mValidator != null && !mValidator.isValid(destination)))) {
14983bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            entry = RecipientEntry.constructFakeEntry(destination);
14993bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        } else {
15003bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            entry = item;
15013bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
15023bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        return entry;
15033bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
15043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
1505cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1506cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /* package */ Collection<Long> getContactIds() {
1507cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        final Set<Long> result = new HashSet<Long>();
1508aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        RecipientChip[] chips = getSortedRecipients();
1509df4457285cf0a54d957f1fad3bbc07112f750818Mindy Pereira        if (chips != null) {
1510df4457285cf0a54d957f1fad3bbc07112f750818Mindy Pereira            for (RecipientChip chip : chips) {
1511df4457285cf0a54d957f1fad3bbc07112f750818Mindy Pereira                result.add(chip.getContactId());
1512df4457285cf0a54d957f1fad3bbc07112f750818Mindy Pereira            }
1513cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1514cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return result;
1515cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1516cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1517aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
1518aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1519aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /* package */ Collection<Long> getDataIds() {
1520aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        final Set<Long> result = new HashSet<Long>();
1521aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        RecipientChip [] chips = getSortedRecipients();
1522aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (chips != null) {
1523aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            for (RecipientChip chip : chips) {
1524aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                result.add(chip.getDataId());
1525aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            }
1526aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
1527aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        return result;
1528d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira    }
1529d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira
153001162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
1531aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /* package */RecipientChip[] getSortedRecipients() {
1532aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        RecipientChip[] recips = getSpannable()
1533aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                .getSpans(0, getText().length(), RecipientChip.class);
1534e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
1535aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                .asList(recips));
1536e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        final Spannable spannable = getSpannable();
1537e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
1538e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
1539e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            @Override
1540e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            public int compare(RecipientChip first, RecipientChip second) {
1541e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int firstStart = spannable.getSpanStart(first);
1542e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int secondStart = spannable.getSpanStart(second);
1543e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                if (firstStart < secondStart) {
1544e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return -1;
1545e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                } else if (firstStart > secondStart) {
1546e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return 1;
1547e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                } else {
1548e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return 0;
1549e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                }
1550e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            }
1551e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        });
1552e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
1553e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    }
1554e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
155512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
155612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
155712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
155812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
155912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
156012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
156112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
156212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
156312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
156412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
156512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
156612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
156712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
156812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1569a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1570a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * No chips are selectable.
1571a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
157212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
157312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
157412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
157512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
157612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1577d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
1578d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ImageSpan getMoreChip() {
1579dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
1580dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                MoreImageSpan.class);
1581dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
1582dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    }
1583dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira
1584f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private MoreImageSpan createMoreSpan(int count) {
1585f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), count);
1586f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1587f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1588f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1589f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1590f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                + mMoreItem.getPaddingRight();
1591f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int height = getLineHeight();
1592f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1593f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Canvas canvas = new Canvas(drawable);
1594f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int adjustedHeight = height;
1595f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Layout layout = getLayout();
1596f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (layout != null) {
1597f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            adjustedHeight -= layout.getLineDescent(0);
1598f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1599f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
1600f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1601f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
1602f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        result.setBounds(0, 0, width, height);
1603f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return new MoreImageSpan(result);
1604f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1605f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1606f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1607f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ void createMoreChipPlainText() {
1608f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        // Take the first <= CHIP_LIMIT addresses and get to the end of the second one.
1609f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Editable text = getText();
1610f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int start = 0;
1611f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int end = start;
1612f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        for (int i = 0; i < CHIP_LIMIT; i++) {
1613f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            end = movePastTerminators(mTokenizer.findTokenEnd(text, start));
1614f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            start = end; // move to the next token and get its end.
1615f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1616f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        // Now, count total addresses.
1617f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        start = 0;
1618f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int tokenCount = countTokens(text);
1619f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT);
1620f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(end, text.length()));
1621f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1622f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        text.replace(end, text.length(), chipText);
1623f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        mMoreChip = moreSpan;
1624f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1625f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1626f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1627f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /* package */int countTokens(Editable text) {
1628f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int tokenCount = 0;
1629f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int start = 0;
1630f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        while (start < text.length()) {
1631f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            start = movePastTerminators(mTokenizer.findTokenEnd(text, start));
1632f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            tokenCount++;
1633f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            if (start >= text.length()) {
1634f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                break;
1635f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            }
1636f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1637f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return tokenCount;
1638f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1639f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1640a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1641342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1642342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * do not fit in the pre-defined available space when the
1643342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * RecipientEditTextView loses focus.
1644a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1645d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
1646d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ void createMoreChip() {
1647f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (mNoChips) {
1648f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            createMoreChipPlainText();
1649f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            return;
1650f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1651f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1652076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        if (!mShouldShrink) {
1653076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira            return;
1654076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        }
1655076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira
1656e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
1657e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        if (tempMore.length > 0) {
1658e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
1659e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        }
1660e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        RecipientChip[] recipients = getSortedRecipients();
1661f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1662d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
16633bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            mMoreChip = null;
16643bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            return;
166512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
1666e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        Spannable spannable = getSpannable();
1667d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira        int numRecipients = recipients.length;
166812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1669f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(overage);
167021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        mRemovedSpans = new ArrayList<RecipientChip>();
167112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int totalReplaceStart = 0;
167212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int totalReplaceEnd = 0;
1673e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        Editable text = getText();
167421cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
167521cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            mRemovedSpans.add(recipients[i]);
1676364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            if (i == numRecipients - overage) {
167721cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
1678364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            }
167921cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            if (i == recipients.length - 1) {
168021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
1681364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            }
1682e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
1683e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
1684e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
1685e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
16861852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
168721cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            spannable.removeSpan(recipients[i]);
168812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
1689f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (totalReplaceEnd < text.length()) {
1690f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            totalReplaceEnd = text.length();
1691f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
16921852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
16931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
16941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
169512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
16961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        text.replace(start, end, chipText);
16973bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        mMoreChip = moreSpan;
169812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
169912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1700a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1701a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
1702a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
1703a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1704d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
1705d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /*package*/ void removeMoreChip() {
170612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        if (mMoreChip != null) {
170712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            Spannable span = getSpannable();
170812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            span.removeSpan(mMoreChip);
170912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            mMoreChip = null;
171012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            // Re-add the spans that were removed.
171112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
171212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                // Recreate each removed span.
1713ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                RecipientChip[] recipients = getSortedRecipients();
171454effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                // Start the search for tokens after the last currently visible
171554effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                // chip.
1716ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                if (recipients == null || recipients.length == 0) {
1717ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                    return;
1718ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                }
171954effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
172012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                Editable editable = getText();
172112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                for (RecipientChip chip : mRemovedSpans) {
1722e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    int chipStart;
17233bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    int chipEnd;
17243bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    String token;
1725e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    // Need to find the location of the chip, again.
1726e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    token = (String) chip.getOriginalText();
172754effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // As we find the matching recipient for the remove spans,
172854effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // reduce the size of the string we need to search.
172954effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // That way, if there are duplicates, we always find the correct
173054effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // recipient.
173154effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    chipStart = editable.toString().indexOf(token, end);
173254effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
17330d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    // Only set the span if we found a matching token.
17340d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    if (chipStart != -1) {
17350d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
17360d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
17370d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    }
173812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                }
173912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                mRemovedSpans.clear();
174012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            }
174112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
174212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
174312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1744cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /**
174595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
174695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
174795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
174895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
174995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * will change the background color of the chip, show the delete icon,
175095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * and a popup window with the address in use highlighted and any other
175195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * alternate addresses for the contact.
175295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @param currentChip Chip to select.
175395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
175495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * just contained an email address.
1755cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     */
1756aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private RecipientChip selectChip(RecipientChip currentChip) {
175701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
175801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            CharSequence text = currentChip.getValue();
175901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            Editable editable = getText();
176001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            removeChip(currentChip);
176101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            editable.append(text);
176201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            setCursorVisible(true);
176301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            setSelection(editable.length());
1764f908b64e75e0bb38ff2806232ca57c93189156eaMindy Pereira            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
176501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
176695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int start = getChipStart(currentChip);
176795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int end = getChipEnd(currentChip);
176895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            getSpannable().removeSpan(currentChip);
176995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            RecipientChip newChip;
177095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            try {
1771f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (mNoChips) {
1772f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    return null;
1773f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
177495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
177595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            } catch (NullPointerException e) {
177695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
177795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                return null;
177895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
17790ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira            Editable editable = getText();
178095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
1781d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            if (start == -1 || end == -1) {
178295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
1783d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            } else {
178404da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1785d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            }
178695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            newChip.setSelected(true);
178701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
178801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
178981fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira            }
179001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            showAddress(newChip, mAddressPopup, getWidth(), getContext());
17914e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            setCursorVisible(false);
179295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            return newChip;
179395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        } else {
179401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            int start = getChipStart(currentChip);
179501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            int end = getChipEnd(currentChip);
179601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            getSpannable().removeSpan(currentChip);
179701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            RecipientChip newChip;
179801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            try {
179901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                newChip = constructChipSpan(currentChip.getEntry(), start, true);
180001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            } catch (NullPointerException e) {
180101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                Log.e(TAG, e.getMessage(), e);
180201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                return null;
180301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
180495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            Editable editable = getText();
180501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
180601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (start == -1 || end == -1) {
180701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
180801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            } else {
180901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
181001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
181101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            newChip.setSelected(true);
181201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
181301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
181401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
181501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
18164e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            setCursorVisible(false);
181701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            return newChip;
18180ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira        }
181995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
18200ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira
1821cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
182201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
182301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            int width, Context context) {
182401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
182501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
182601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        // Align the alternates popup with the left side of the View,
182701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        // regardless of the position of the chip tapped.
1828ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        setEnabled(false);
182901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setWidth(width);
183001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setAnchorView(this);
183101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setVerticalOffset(bottom);
183201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
183301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
183401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            @Override
183501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
183601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                unselectChip(currentChip);
183701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                popup.dismiss();
183801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
183901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        });
184001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.show();
184101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        ListView listView = popup.getListView();
184201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
184301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        listView.setItemChecked(0, true);
184401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
184501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
184695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
184795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
1848f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira     * the chip without a delete icon and with an unfocused background. This is
1849f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira     * called when the RecipientChip no longer has focus.
185095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
1851aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private void unselectChip(RecipientChip chip) {
185295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int start = getChipStart(chip);
185395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int end = getChipEnd(chip);
185495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable editable = getText();
1855e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira        mSelectedChip = null;
185695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (start == -1 || end == -1) {
1857f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
1858c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            setSelection(editable.length());
1859c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            commitDefault();
186095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        } else {
1861e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira            getSpannable().removeSpan(chip);
186295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
186304da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            editable.removeSpan(chip);
186404da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            try {
1865f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (!mNoChips) {
1866f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
1867f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1868f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
186904da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            } catch (NullPointerException e) {
187004da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira                Log.e(TAG, e.getMessage(), e);
187104da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            }
1872cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
187395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setCursorVisible(true);
187495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setSelection(editable.length());
187595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
187695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mAlternatesPopup.dismiss();
1877cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
187895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
1879cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
188095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
188195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Return whether a touch event was inside the delete target of
188295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * a selected chip. It is in the delete target if:
188395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * 1) the x and y points of the event are within the
188495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * delete assset.
188595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
188695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * right after the selected chip.
188795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @return boolean
188895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
188995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
189095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Figure out the bounds of this chip and whether or not
189195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // the user clicked in the X portion.
189295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return chip.isSelected() && offset == getChipEnd(chip);
189395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
18945086391a478c3b1badbb86074c3cef72126c7d0fMindy Pereira
189595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
189695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
189795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
189897cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
189997cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    /*pacakge*/ void removeChip(RecipientChip chip) {
190095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Spannable spannable = getSpannable();
190195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
190295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
190395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable text = getText();
190462397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        int toDelete = spanEnd;
190595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
190695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Clear that there is a selected chip before updating any text.
190795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
190895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mSelectedChip = null;
1909cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
191062397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        // Always remove trailing spaces when removing a chip.
191162397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
191262397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira            toDelete++;
191362397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        }
191495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        spannable.removeSpan(chip);
191562397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        text.delete(spanStart, toDelete);
191695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
191795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            clearSelectedChip();
1918cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
191995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
1920cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
192195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
192295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Replace this currently selected chip with a new chip
192395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * that uses the contact data provided.
192495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
1925aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
1926aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) {
192795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
192895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
192995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mSelectedChip = null;
1930cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
193195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int start = getChipStart(chip);
193295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int end = getChipEnd(chip);
193395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        getSpannable().removeSpan(chip);
193495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable editable = getText();
193595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        CharSequence chipText = createChip(entry, false);
19362d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        if (chipText != null) {
19372d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            if (start == -1 || end == -1) {
19382d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
19392d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                editable.insert(0, chipText);
19402d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            } else {
19412d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
19422d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    // There may be a space to replace with this chip's new
19432d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    // associated
19442d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    // space. Check for it
19452d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    int toReplace = end;
19462d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
19472d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
19482d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                        toReplace++;
19492d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    }
19502d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    editable.replace(start, toReplace, chipText);
195162397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira                }
19527ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira            }
1953cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
195495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setCursorVisible(true);
195595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
195695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            clearSelectedChip();
1957cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
195895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
1959cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
196095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
196195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
196295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
196395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Otherwise, unselect the chip.
196495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
196595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    public void onClick(RecipientChip chip, int offset, float x, float y) {
196695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (chip.isSelected()) {
196795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
196895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                removeChip(chip);
196995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            } else {
197095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                clearSelectedChip();
1971cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1972cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
197395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
1974cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
197521cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira    private boolean chipsPending() {
197621cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
197721cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira    }
197821cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira
1979883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    @Override
1980883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
1981883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira        mTextWatcher = null;
1982883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira        super.removeTextChangedListener(watcher);
1983883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    }
1984883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira
198579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private class RecipientTextWatcher implements TextWatcher {
198672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira
198779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
198879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void afterTextChanged(Editable s) {
198932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // If the text has been set to null or empty, make sure we remove
199032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // all the spans we applied.
199132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            if (TextUtils.isEmpty(s)) {
199232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                // Remove all the chips spans.
199332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                Spannable spannable = getSpannable();
199432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
199532366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                        RecipientChip.class);
199632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                for (RecipientChip chip : chips) {
199732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                    spannable.removeSpan(chip);
199832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                }
199932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                if (mMoreChip != null) {
200032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                    spannable.removeSpan(mMoreChip);
200132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                }
200232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                return;
200332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            }
200401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            // Get whether there are any recipients pending addition to the
200501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            // view. If there are, don't do anything in the text watcher.
200621cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            if (chipsPending()) {
200779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                return;
200879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
2009c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            // If the user is editing a chip, don't clear it.
2010c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            if (mSelectedChip != null
2011c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
201279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                setCursorVisible(true);
201379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                setSelection(getText().length());
201479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                clearSelectedChip();
201579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
201679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int length = s.length();
201779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // Make sure there is content there to parse and that it is
2018ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira            // not just the commit character.
201979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (length > 1) {
2020ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                char last;
202111a2adb53f764ffcadd262678ff782e6b4992decMindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
2022ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                int len = length() - 1;
2023ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                if (end != len) {
2024ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                    last = s.charAt(end);
2025ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                } else {
2026ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                    last = s.charAt(len);
2027ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                }
202879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
202979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    commitByCharacter();
203079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                } else if (last == COMMIT_CHAR_SPACE) {
203179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    // Check if this is a valid email address. If it is,
203279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    // commit it.
203379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    String text = getText().toString();
203479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
203579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
203679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                            tokenStart));
2037fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira                    if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) {
203879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                        commitByCharacter();
203979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    }
204079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                }
204179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
204279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
204379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
204479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
204579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
204672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            // This is a delete; check to see if the insertion point is on a space
204772a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            // following a chip.
204872a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            if (before > count) {
204972a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                // If the item deleted is a space, and the thing before the
205072a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                // space is a chip, delete the entire span.
205172a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                int selStart = getSelectionStart();
205272a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                RecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
205372a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                        RecipientChip.class);
205472a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                if (repl.length > 0) {
205572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    // There is a chip there! Just remove it.
205672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    Editable editable = getText();
205772a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    // Add the separator token.
205872a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    int tokenStart = mTokenizer.findTokenStart(editable, selStart);
205972a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    int tokenEnd = mTokenizer.findTokenEnd(editable, tokenStart);
206062fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    tokenEnd = tokenEnd + 1;
206162fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    if (tokenEnd > editable.length()) {
206262fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                        tokenEnd = editable.length();
206362fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    }
206462fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    editable.delete(tokenStart, tokenEnd);
206572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    getSpannable().removeSpan(repl[0]);
206672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                }
206772a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            }
206879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
206979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
207079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
207179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2072dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            // Do nothing.
207379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
207479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
20751852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
2076e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2077e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
2078e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2079e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private void handlePasteClip(ClipData clip) {
2080e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        removeTextChangedListener(mTextWatcher);
2081e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2082e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
2083e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            for (int i = 0; i < clip.getItemCount(); i++) {
2084e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                CharSequence paste = clip.getItemAt(i).getText();
2085e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                if (paste != null) {
2086e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    int start = getSelectionStart();
2087e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    int end = getSelectionEnd();
2088e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    Editable editable = getText();
2089e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    if (start >= 0 && end >= 0 && start != end) {
2090e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                        editable.append(paste, start, end);
2091e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    } else {
2092e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                        editable.insert(end, paste);
2093e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    }
2094e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    handlePasteAndReplace();
2095e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                }
2096e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            }
2097e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2098e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2099e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        mHandler.post(mAddTextWatcher);
2100e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2101e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
210220c9d620e79ae28994856541761a951074551518Mindy Pereira    @Override
210320c9d620e79ae28994856541761a951074551518Mindy Pereira    public boolean onTextContextMenuItem(int id) {
210420c9d620e79ae28994856541761a951074551518Mindy Pereira        if (id == android.R.id.paste) {
210520c9d620e79ae28994856541761a951074551518Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
210620c9d620e79ae28994856541761a951074551518Mindy Pereira                    Context.CLIPBOARD_SERVICE);
2107e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            handlePasteClip(clipboard.getPrimaryClip());
210820c9d620e79ae28994856541761a951074551518Mindy Pereira            return true;
210920c9d620e79ae28994856541761a951074551518Mindy Pereira        }
211020c9d620e79ae28994856541761a951074551518Mindy Pereira        return super.onTextContextMenuItem(id);
211120c9d620e79ae28994856541761a951074551518Mindy Pereira    }
211220c9d620e79ae28994856541761a951074551518Mindy Pereira
2113b4e244af14950aee7d612612d5406981315d3454Mindy Pereira    private void handlePasteAndReplace() {
2114b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        ArrayList<RecipientChip> created = handlePaste();
2115b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        if (created != null && created.size() > 0) {
2116b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            // Perform reverse lookups on the pasted contacts.
2117b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            IndividualReplacementTask replace = new IndividualReplacementTask();
2118b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            replace.execute(created);
2119b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        }
2120b4e244af14950aee7d612612d5406981315d3454Mindy Pereira    }
2121b4e244af14950aee7d612612d5406981315d3454Mindy Pereira
212220c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
2123b4e244af14950aee7d612612d5406981315d3454Mindy Pereira    /* package */ArrayList<RecipientChip> handlePaste() {
212420c9d620e79ae28994856541761a951074551518Mindy Pereira        String text = getText().toString();
212520c9d620e79ae28994856541761a951074551518Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
212620c9d620e79ae28994856541761a951074551518Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
212720c9d620e79ae28994856541761a951074551518Mindy Pereira        int tokenStart = originalTokenStart;
212820c9d620e79ae28994856541761a951074551518Mindy Pereira        int prevTokenStart = tokenStart;
212920c9d620e79ae28994856541761a951074551518Mindy Pereira        RecipientChip findChip = null;
2130b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        ArrayList<RecipientChip> created = new ArrayList<RecipientChip>();
213120c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenStart != 0) {
213220c9d620e79ae28994856541761a951074551518Mindy Pereira            // There are things before this!
213320c9d620e79ae28994856541761a951074551518Mindy Pereira            while (tokenStart != 0 && findChip == null) {
213420c9d620e79ae28994856541761a951074551518Mindy Pereira                prevTokenStart = tokenStart;
213520c9d620e79ae28994856541761a951074551518Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
213620c9d620e79ae28994856541761a951074551518Mindy Pereira                findChip = findChip(tokenStart);
213720c9d620e79ae28994856541761a951074551518Mindy Pereira            }
213820c9d620e79ae28994856541761a951074551518Mindy Pereira            if (tokenStart != originalTokenStart) {
213920c9d620e79ae28994856541761a951074551518Mindy Pereira                if (findChip != null) {
214020c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenStart = prevTokenStart;
214120c9d620e79ae28994856541761a951074551518Mindy Pereira                }
214220c9d620e79ae28994856541761a951074551518Mindy Pereira                int tokenEnd;
214320c9d620e79ae28994856541761a951074551518Mindy Pereira                RecipientChip createdChip;
214420c9d620e79ae28994856541761a951074551518Mindy Pereira                while (tokenStart < originalTokenStart) {
214520c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
214620c9d620e79ae28994856541761a951074551518Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
214720c9d620e79ae28994856541761a951074551518Mindy Pereira                    createdChip = findChip(tokenStart);
214820c9d620e79ae28994856541761a951074551518Mindy Pereira                    // +1 for the space at the end.
214920c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
2150b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                    created.add(createdChip);
215120c9d620e79ae28994856541761a951074551518Mindy Pereira                }
215220c9d620e79ae28994856541761a951074551518Mindy Pereira            }
215320c9d620e79ae28994856541761a951074551518Mindy Pereira        }
215420c9d620e79ae28994856541761a951074551518Mindy Pereira        // Take a look at the last token. If the token has been completed with a
215520c9d620e79ae28994856541761a951074551518Mindy Pereira        // commit character, create a chip.
215620c9d620e79ae28994856541761a951074551518Mindy Pereira        if (isCompletedToken(lastAddress)) {
215720c9d620e79ae28994856541761a951074551518Mindy Pereira            Editable editable = getText();
2158b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
2159b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            commitChip(tokenStart, editable.length(), editable);
2160b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            created.add(findChip(tokenStart));
216120c9d620e79ae28994856541761a951074551518Mindy Pereira        }
2162b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        return created;
216320c9d620e79ae28994856541761a951074551518Mindy Pereira    }
216420c9d620e79ae28994856541761a951074551518Mindy Pereira
216520c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
216620c9d620e79ae28994856541761a951074551518Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
216720c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenEnd >= length()) {
216820c9d620e79ae28994856541761a951074551518Mindy Pereira            return tokenEnd;
216920c9d620e79ae28994856541761a951074551518Mindy Pereira        }
217020c9d620e79ae28994856541761a951074551518Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
217120c9d620e79ae28994856541761a951074551518Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
217220c9d620e79ae28994856541761a951074551518Mindy Pereira            tokenEnd++;
217320c9d620e79ae28994856541761a951074551518Mindy Pereira        }
217420c9d620e79ae28994856541761a951074551518Mindy Pereira        // This token had not only an end token character, but also a space
217520c9d620e79ae28994856541761a951074551518Mindy Pereira        // separating it from the next token.
217620c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
217720c9d620e79ae28994856541761a951074551518Mindy Pereira            tokenEnd++;
217820c9d620e79ae28994856541761a951074551518Mindy Pereira        }
217920c9d620e79ae28994856541761a951074551518Mindy Pereira        return tokenEnd;
218020c9d620e79ae28994856541761a951074551518Mindy Pereira    }
218120c9d620e79ae28994856541761a951074551518Mindy Pereira
21821852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
21831852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        private RecipientChip createFreeChip(RecipientEntry entry) {
21841852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            try {
2185f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (mNoChips) {
2186f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    return null;
2187f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
21881852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                return constructChipSpan(entry, -1, false);
21891852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            } catch (NullPointerException e) {
21901852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                Log.e(TAG, e.getMessage(), e);
21911852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                return null;
21921852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
21931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
21941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
21951852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        @Override
21961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        protected Void doInBackground(Void... params) {
21971852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            if (mIndividualReplacements != null) {
21981852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                mIndividualReplacements.cancel(true);
21991852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
22001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // For each chip in the list, look up the matching contact.
22011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // If there is a match, replace that chip with the matching
22021852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // chip.
22031852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
2204e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            RecipientChip[] existingChips = getSortedRecipients();
22051852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
22061852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                originalRecipients.add(existingChips[i]);
22071852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
22081852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            if (mRemovedSpans != null) {
22091852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                originalRecipients.addAll(mRemovedSpans);
22101852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
22111852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
2212f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira            RecipientChip chip;
22131852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2214f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                chip = originalRecipients.get(i);
2215f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                if (chip != null) {
2216f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                    addresses[i] = createAddressText(chip.getEntry());
2217f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                }
22181852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
22191852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
22201852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
22211852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
22221852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
22231852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                RecipientEntry entry = null;
222401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
22251852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
22261852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    // Replace this.
222701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
222801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                            .getDestination())));
22291852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                }
22301852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                if (entry != null) {
22311852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    replacements.add(createFreeChip(entry));
22321852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                } else {
22331852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    replacements.add(temp);
22341852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                }
22351852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
22361852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            if (replacements != null && replacements.size() > 0) {
22371852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                mHandler.post(new Runnable() {
22381852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    @Override
22391852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    public void run() {
22401852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
22411852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                                .toString());
22421852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        Editable oldText = getText();
22431852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        int start, end;
22441852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        int i = 0;
22451852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        for (RecipientChip chip : originalRecipients) {
22461852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            start = oldText.getSpanStart(chip);
22471852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            if (start != -1) {
22487ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira                                end = oldText.getSpanEnd(chip);
2249c6f163c2b5a2da5b808c8cbf87c11d891bce9d25Mindy Pereira                                oldText.removeSpan(chip);
22507ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira                                // Leave a spot for the space!
2251e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                                RecipientChip replacement = replacements.get(i);
2252e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                                text.setSpan(replacement, start, end,
22537ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
2254e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                                replacement.setOriginalText(text.toString().substring(start, end));
22551852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            }
22561852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            i++;
22571852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        }
22581852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        originalRecipients.clear();
2259c6f163c2b5a2da5b808c8cbf87c11d891bce9d25Mindy Pereira                        setText(text);
22601852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    }
22611852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                });
22621852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
22631852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            return null;
22641852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
22651852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    }
22661852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
22671852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
22681852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        @SuppressWarnings("unchecked")
22691852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        @Override
22701852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        protected Void doInBackground(Object... params) {
22711852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // For each chip in the list, look up the matching contact.
22721852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // If there is a match, replace that chip with the matching
22731852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // chip.
22741852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            final ArrayList<RecipientChip> originalRecipients =
22751852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                (ArrayList<RecipientChip>) params[0];
22761852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            String[] addresses = new String[originalRecipients.size()];
2277f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira            RecipientChip chip;
22781852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2279f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                chip = originalRecipients.get(i);
2280f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                if (chip != null) {
2281f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                    addresses[i] = createAddressText(chip.getEntry());
2282f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                }
22831852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
22841852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
22851852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    .getMatchingRecipients(getContext(), addresses);
22861852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (final RecipientChip temp : originalRecipients) {
228701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
22881852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        && getSpannable().getSpanStart(temp) != -1) {
22891852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    // Replace this.
229001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                    final RecipientEntry entry = createValidatedEntry(entries
2291b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                            .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase()));
22921852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    if (entry != null) {
22931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        mHandler.post(new Runnable() {
22941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            @Override
22951852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            public void run() {
22961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                                replaceChip(temp, entry);
22971852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            }
22981852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        });
22991852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                    }
23001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                }
23011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
23021852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            return null;
23031852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
23041852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    }
23051d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
2306e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
2307e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    /**
2308e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
2309e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     * more chip across activity restarts/
2310e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     */
2311e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    private class MoreImageSpan extends ImageSpan {
2312e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        public MoreImageSpan(Drawable b) {
2313e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            super(b);
2314e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        }
2315e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    }
2316e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
23171d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
23181d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onDown(MotionEvent e) {
23191d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
23201d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
23211d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
23221d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
23231d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
23241d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
23251d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
23261d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
23271d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
23281d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
23291d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onLongPress(MotionEvent event) {
23301d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        if (mSelectedChip != null) {
23311d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira            return;
23321d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
23331d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        float x = event.getX();
23341d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        float y = event.getY();
23351d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        int offset = putOffsetInRange(getOffsetForPosition(x, y));
23361d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        RecipientChip currentChip = findChip(offset);
23371d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        if (currentChip != null) {
2338e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            if (mDragEnabled) {
2339e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Start drag-and-drop for the selected chip.
2340e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                startDrag(currentChip);
2341e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            } else {
2342e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Copy the selected chip email address.
2343e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                showCopyDialog(currentChip.getEntry().getDestination());
2344e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            }
2345e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2346e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2347e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2348e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2349e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Enables drag-and-drop for chips.
2350e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2351e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    public void enableDrag() {
2352e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        mDragEnabled = true;
2353e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2354e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2355e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2356e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Starts drag-and-drop for the selected chip.
2357e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2358e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private void startDrag(RecipientChip currentChip) {
2359e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        String address = currentChip.getEntry().getDestination();
2360e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
2361e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2362e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // Start drag mode.
2363e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
2364e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2365e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // Remove the current chip, so drag-and-drop will result in a move.
2366e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // TODO (phamm): consider readd this chip if it's dropped outside a target.
2367e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        removeChip(currentChip);
2368e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2369e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2370e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2371e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Handles drag event.
2372e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2373e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    @Override
2374e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    public boolean onDragEvent(DragEvent event) {
2375e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        switch (event.getAction()) {
2376e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DRAG_STARTED:
2377e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Only handle plain text drag and drop.
2378e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
2379e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DRAG_ENTERED:
2380e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                requestFocus();
2381e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return true;
2382e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DROP:
2383e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                handlePasteClip(event.getClipData());
2384e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return true;
2385e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2386e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        return false;
2387e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2388e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2389e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2390e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Drag shadow for a {@link RecipientChip}.
2391e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2392e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private final class RecipientChipShadow extends DragShadowBuilder {
2393e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        private final RecipientChip mChip;
2394e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2395e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        public RecipientChipShadow(RecipientChip chip) {
2396e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            mChip = chip;
2397e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2398e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2399e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        @Override
2400e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
2401e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            Rect rect = mChip.getDrawable().getBounds();
2402e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            shadowSize.set(rect.width(), rect.height());
2403e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            shadowTouchPoint.set(rect.centerX(), rect.centerY());
2404e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2405e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2406e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        @Override
2407e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        public void onDrawShadow(Canvas canvas) {
2408e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            mChip.getDrawable().draw(canvas);
24091d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
24101d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
24111d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
24121d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private void showCopyDialog(final String address) {
24131d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyAddress = address;
24141d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setTitle(address);
241576e62e334ef2b9ec55e5395f8374072f7ee1ea84Mindy Pereira        mCopyDialog.setContentView(R.layout.copy_chip_dialog_layout);
24161d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setCancelable(true);
24171d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
241880f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        Button button = (Button)mCopyDialog.findViewById(android.R.id.button1);
241980f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        button.setOnClickListener(this);
242080f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        int btnTitleId;
242180f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        if (((BaseRecipientAdapter)getAdapter()).getQueryType() ==
242280f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor                BaseRecipientAdapter.QUERY_TYPE_PHONE) {
242380f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor            btnTitleId = R.string.copy_number;
242480f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        } else {
242580f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor            btnTitleId = R.string.copy_email;
242680f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        }
242780f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        String buttonTitle = getContext().getResources().getString(btnTitleId);
242880f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        button.setText(buttonTitle);
24291d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setOnDismissListener(this);
24301d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.show();
24311d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
24321d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
24331d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
24341d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
24351d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
24361d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
24371d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
24381d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
24391d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
24401d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onShowPress(MotionEvent e) {
24411d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
24421d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
24431d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
24441d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
24451d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
24461d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
24471d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
24481d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
24491d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
24501d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
24511d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onDismiss(DialogInterface dialog) {
24521d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyAddress = null;
24531d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
24541d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
24551d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
24561d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onClick(View v) {
24571d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Copy this to the clipboard.
24581d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
24591d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira                Context.CLIPBOARD_SERVICE);
24601d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
24611d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.dismiss();
24621d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
24631852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira}
2464