RecipientEditTextView.java revision 35e82d4f9522906f7953667cf5c5f8137ec2f5ac
19159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira/*
2194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy
39159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Copyright (C) 2011 The Android Open Source Project
49159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *
59159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Licensed under the Apache License, Version 2.0 (the "License");
69159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * you may not use this file except in compliance with the License.
79159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * You may obtain a copy of the License at
89159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *
99159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *      http://www.apache.org/licenses/LICENSE-2.0
109159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira *
119159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * Unless required by applicable law or agreed to in writing, software
129159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * distributed under the License is distributed on an "AS IS" BASIS,
139159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
149159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * See the License for the specific language governing permissions and
159159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * limitations under the License.
169159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira */
179159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
189159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereirapackage com.android.ex.chips;
199159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
201d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.app.Dialog;
211d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.ClipData;
2220c9d620e79ae28994856541761a951074551518Mindy Pereiraimport android.content.ClipDescription;
231d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.ClipboardManager;
249159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.content.Context;
251d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.DialogInterface;
261d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.content.DialogInterface.OnDismissListener;
2752c441e2c03e0f48572348953b985a4bf989c057Mindy Pereiraimport android.content.res.Resources;
2822faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereiraimport android.content.res.TypedArray;
29cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Bitmap;
302bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.BitmapFactory;
31cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Canvas;
322bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.Matrix;
33b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Linimport android.graphics.Paint;
34e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Phamimport android.graphics.Point;
35cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.Rect;
362bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereiraimport android.graphics.RectF;
37cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.graphics.drawable.BitmapDrawable;
389159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.graphics.drawable.Drawable;
391852931de1e24e77cb708f4ba010eaa269426657Mindy Pereiraimport android.os.AsyncTask;
401650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedyimport android.os.Build;
41007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.os.Handler;
4278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedyimport android.os.Looper;
43007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.os.Message;
44dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereiraimport android.os.Parcelable;
459159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.text.Editable;
4661b48ccefc655549802556947eb8cf3959c6ddadGilles Debunneimport android.text.InputType;
47cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Layout;
48cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Spannable;
49cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.SpannableString;
500e128968f242866568bba0f833bb17ffda127f07Scott Kennedyimport android.text.SpannableStringBuilder;
51cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.Spanned;
52cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.TextPaint;
53cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.TextUtils;
54b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereiraimport android.text.TextWatcher;
55cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.method.QwertyKeyListener;
56cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.text.style.ImageSpan;
573bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereiraimport android.text.util.Rfc822Token;
583bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereiraimport android.text.util.Rfc822Tokenizer;
599159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.util.AttributeSet;
60cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.util.Log;
61093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindypimport android.util.TypedValue;
62fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.ActionMode;
63aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadlerimport android.view.ActionMode.Callback;
64e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Phamimport android.view.DragEvent;
651d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereiraimport android.view.GestureDetector;
66cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.KeyEvent;
67750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereiraimport android.view.LayoutInflater;
68fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.Menu;
69fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereiraimport android.view.MenuItem;
70cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.MotionEvent;
71cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.view.View;
7261b48ccefc655549802556947eb8cf3959c6ddadGilles Debunneimport android.view.View.OnClickListener;
73c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereiraimport android.view.ViewParent;
7493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereiraimport android.view.inputmethod.EditorInfo;
7593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereiraimport android.view.inputmethod.InputConnection;
76cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.AdapterView;
77cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.AdapterView.OnItemClickListener;
7880f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylorimport android.widget.Button;
79093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindypimport android.widget.Filterable;
8095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereiraimport android.widget.ListAdapter;
81cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereiraimport android.widget.ListPopupWindow;
82007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereiraimport android.widget.ListView;
839159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereiraimport android.widget.MultiAutoCompleteTextView;
84c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereiraimport android.widget.ScrollView;
85750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereiraimport android.widget.TextView;
867537f840506bcb642bed9dc1c2bdcf6d31c6b2a7Daisuke Miyakawa
8716923ee63a79fce4be3f62b08bcd1f80617c1205mindypimport com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback;
88194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedyimport com.android.ex.chips.recipientchip.DrawableRecipientChip;
89194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedyimport com.android.ex.chips.recipientchip.InvisibleRecipientChip;
90194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedyimport com.android.ex.chips.recipientchip.VisibleRecipientChip;
91cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
9276f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.ArrayList;
9376f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Arrays;
9476f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Collections;
9576f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Comparator;
9676f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.List;
9776f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Map;
9876f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Set;
9976f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.regex.Matcher;
10076f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.regex.Pattern;
10176f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert
1029159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira/**
1039159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
1049159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * that use the new Chips UI for addressing a message to recipients.
1059159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira */
10695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
1071d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
108ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
1096a6bc7010f0f634ad32312d9802c50ed749f12bbMindy Pereira        TextView.OnEditorActionListener {
110cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
111aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
112aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
113aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
114aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
115aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
116aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
11703e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy    private static final String SEPARATOR = String.valueOf(COMMIT_CHAR_COMMA)
11803e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy            + String.valueOf(COMMIT_CHAR_SPACE);
11903e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy
120cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private static final String TAG = "RecipientEditTextView";
121cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
122f11483ad7d1e7abbce9d59ecf01b4b904a777b05Scott Kennedy    private static final int DISMISS = "dismiss".hashCode();
123aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
124aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final long DISMISS_DELAY = 300;
125aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
12612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
127f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
128f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ static final int CHIP_LIMIT = 2;
129f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
130f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private static final int MAX_CHIPS_PARSED = 50;
13112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
132aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static int sSelectedTextColor = -1;
133aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
134aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Resources for displaying chips.
135cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Drawable mChipBackground = null;
136cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
137cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Drawable mChipDelete = null;
138cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
139aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Drawable mInvalidChipBackground;
140aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
141aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Drawable mChipBackgroundPressed;
142aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
143aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private float mChipHeight;
144aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
145aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private float mChipFontSize;
146aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
147ffd270a55d80db66bd6a1d7b786443fdc00af372Mindy Pereira    private float mLineSpacingExtra;
148ffd270a55d80db66bd6a1d7b786443fdc00af372Mindy Pereira
149cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private int mChipPadding;
150cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
151b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
152b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Enumerator for avatar position. See attr.xml for more details.
153c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * 0 for end, 1 for start.
154b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
155b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private int mAvatarPosition;
156b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
157c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    private static final int AVATAR_POSITION_END = 0;
158b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
159c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    private static final int AVATAR_POSITION_START = 1;
160b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
1619b398e3019d1e12c661647a277a338975a50d952Kevin Lin    /**
1629b398e3019d1e12c661647a277a338975a50d952Kevin Lin     * Enumerator for image span alignment. See attr.xml for more details.
1639b398e3019d1e12c661647a277a338975a50d952Kevin Lin     * 0 for bottom, 1 for baseline.
1649b398e3019d1e12c661647a277a338975a50d952Kevin Lin     */
1659b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private int mImageSpanAlignment;
1669b398e3019d1e12c661647a277a338975a50d952Kevin Lin
1679b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private static final int IMAGE_SPAN_ALIGNMENT_BOTTOM = 0;
1689b398e3019d1e12c661647a277a338975a50d952Kevin Lin
1699b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private static final int IMAGE_SPAN_ALIGNMENT_BASELINE = 1;
1709b398e3019d1e12c661647a277a338975a50d952Kevin Lin
1719b398e3019d1e12c661647a277a338975a50d952Kevin Lin
172b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private boolean mDisableDelete;
173b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
174cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Tokenizer mTokenizer;
175cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
176aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Validator mValidator;
177cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
178194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private DrawableRecipientChip mSelectedChip;
179cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1802bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    private Bitmap mDefaultContactPhoto;
1812bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
18212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private ImageSpan mMoreChip;
18312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
184750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereira    private TextView mMoreItem;
18512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
18620c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    // VisibleForTesting
18720c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    final ArrayList<String> mPendingChips = new ArrayList<String>();
188f97eb41a7946f2c3013ac74f2451b78070531125Mindy Pereira
189007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira    private Handler mHandler;
190007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira
19102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    private int mPendingChipsCount = 0;
19202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
193f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private boolean mNoChips = false;
194f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
19595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private ListPopupWindow mAlternatesPopup;
19695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
19701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    private ListPopupWindow mAddressPopup;
19801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
19920c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    // VisibleForTesting
200194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    ArrayList<DrawableRecipientChip> mTemporaryRecipients;
2011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
202194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private ArrayList<DrawableRecipientChip> mRemovedSpans;
2033bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
204076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    private boolean mShouldShrink = true;
205076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira
2061d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    // Chip copy fields.
2071d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private GestureDetector mGestureDetector;
2081d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
2091d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private Dialog mCopyDialog;
2101d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
2111d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private String mCopyAddress;
2121d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
21395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
214aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
215aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler     * selected chip.
21695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
21795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private OnItemClickListener mAlternatesListener;
21895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
219c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private int mCheckedItem;
220aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
22179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private TextWatcher mTextWatcher;
22279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
223aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Obtain the enclosing scroll view, if it exists, so that the view can be
224aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // scrolled to show the last line of chips content.
225c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private ScrollView mScrollView;
226c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
227aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private boolean mTriedGettingScrollView;
228c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
229e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private boolean mDragEnabled = false;
230e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2314491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor    // This pattern comes from android.util.Patterns. It has been tweaked to handle a "1" before
2324491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor    // parens, so numbers such as "1 (425) 222-2342" match.
2334491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor    private static final Pattern PHONE_PATTERN
2344491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor        = Pattern.compile(                                  // sdd = space, dot, or dash
2354491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor                "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
2364491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor                + "(1?[ ]*\\([0-9]+\\)[\\- \\.]*)?"         // 1(<digits>)<sdd>*
2374491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
2384491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor
23979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
24079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
24179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void run() {
24279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (mTextWatcher == null) {
24379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                mTextWatcher = new RecipientTextWatcher();
24479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                addTextChangedListener(mTextWatcher);
24579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
24679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
24779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    };
24879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
2491852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
2501852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
2512cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
2522cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
2532cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        @Override
2542cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        public void run() {
2552cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            handlePendingChips();
2562cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
2572cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
2582cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    };
2592cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
260d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    private Runnable mDelayedShrink = new Runnable() {
261d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
262d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        @Override
263d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        public void run() {
264d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            shrink();
265d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        }
266d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
267d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    };
268d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
2695e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp    private int mMaxLines;
2705e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp
271093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    private static int sExcessTopPadding = -1;
272093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
273093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    private int mActionBarHeight;
274093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
275fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    private boolean mAttachedToWindow;
276fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler
277b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private DropdownChipLayouter mDropdownChipLayouter;
278b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
2790f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein    private RecipientEntryItemClickedListener mRecipientEntryItemClickedListener;
2800f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein
2810f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein    public interface RecipientEntryItemClickedListener {
2820f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein        /**
2830f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein         * Callback that occurs whenever an auto-complete suggestion is clicked.
2840f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein         * @param charactersTyped the number of characters typed by the user to provide the
2850f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein         *                        auto-complete suggestions.
2860f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein         * @param position the position in the dropdown list that the user clicked
2870f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein         */
2880f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein        void onRecipientEntryItemClicked(int charactersTyped, int position);
2890f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein    }
2900f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein
2919159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2929159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira        super(context, attrs);
29322faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        setChipDimensions(context, attrs);
294e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        if (sSelectedTextColor == -1) {
295e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
296e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        }
29795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
29801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
299bfedc1e199e57dcda494389fdca0750e1f165135Mindy Pereira        mCopyDialog = new Dialog(context);
30095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
30195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            @Override
30295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
30395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                    long rowId) {
30421cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
30595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
30695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        .getRecipientEntry(position));
30795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
30821cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                delayed.obj = mAlternatesPopup;
30995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
31095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                clearComposingText();
31195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
31295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        };
313e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
314b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira        setOnItemClickListener(this);
315fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        setCustomSelectionActionModeCallback(this);
316007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira        mHandler = new Handler() {
317007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            @Override
318007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            public void handleMessage(Message msg) {
319007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                if (msg.what == DISMISS) {
32095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
321007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                    return;
322007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                }
323007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                super.handleMessage(msg);
324007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            }
325007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira        };
3264e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        mTextWatcher = new RecipientTextWatcher();
3274e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        addTextChangedListener(mTextWatcher);
3281d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mGestureDetector = new GestureDetector(context, this);
32993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        setOnEditorActionListener(this);
330b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
331b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        setDropdownChipLayouter(new DropdownChipLayouter(LayoutInflater.from(context), context));
332b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
333b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
3348af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein    public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
335b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        mDropdownChipLayouter = dropdownChipLayouter;
33693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    }
33793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira
3380f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein    public void setRecipientEntryItemClickedListener(RecipientEntryItemClickedListener listener) {
3390f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein        mRecipientEntryItemClickedListener = listener;
3400f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein    }
3410f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein
34293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    @Override
343fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    protected void onDetachedFromWindow() {
3442b2de7c3146a60d39d5d2078052b2c14b6496ef9Tony Mantler        super.onDetachedFromWindow();
345fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        mAttachedToWindow = false;
346fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    }
347fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler
348fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    @Override
349fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    protected void onAttachedToWindow() {
3502b2de7c3146a60d39d5d2078052b2c14b6496ef9Tony Mantler        super.onAttachedToWindow();
351fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        mAttachedToWindow = true;
352fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    }
353fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler
354fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    @Override
35593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) {
35693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        if (action == EditorInfo.IME_ACTION_DONE) {
35793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            if (commitDefault()) {
35893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                return true;
35993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            }
36093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            if (mSelectedChip != null) {
36193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                clearSelectedChip();
36293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                return true;
36393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            } else if (focusNext()) {
36493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                return true;
36593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            }
36693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        }
36793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        return false;
36893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    }
36993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira
37093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    @Override
37193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
37293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        InputConnection connection = super.onCreateInputConnection(outAttrs);
37393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        int imeActions = outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION;
37493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        if ((imeActions&EditorInfo.IME_ACTION_DONE) != 0) {
37593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            // clear the existing action
37693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            outAttrs.imeOptions ^= imeActions;
37793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            // set the DONE action
37893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
37993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        }
38093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
38193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
38293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        }
3831af243fdc3b87979252bddc06097c5056ef93894Scott Kennedy
3841af243fdc3b87979252bddc06097c5056ef93894Scott Kennedy        outAttrs.actionId = EditorInfo.IME_ACTION_DONE;
385a98b8e49be8e1ff1938ab204d86372e292c78194Mindy Pereira        outAttrs.actionLabel = getContext().getString(R.string.done);
38693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        return connection;
387e33555f13a9b05d835cb860e2c30ef40af3c8502Erik    }
3887afe160db4a48f66c964ced89e29e0b63b23c7c1Mindy Pereira
389194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /*package*/ DrawableRecipientChip getLastChip() {
390194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip last = null;
391194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips = getSortedRecipients();
392aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (chips != null && chips.length > 0) {
393aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            last = chips[chips.length - 1];
394e33555f13a9b05d835cb860e2c30ef40af3c8502Erik        }
395aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        return last;
396fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    }
397fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira
3981248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai    /**
3991248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai     * @return The list of {@link RecipientEntry}s that have been selected by the user.
4001248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai     */
4011248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai    public List<RecipientEntry> getSelectedRecipients() {
4021248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai        DrawableRecipientChip[] chips =
4031248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai                getText().getSpans(0, getText().length(), DrawableRecipientChip.class);
4041248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai        List<RecipientEntry> results = new ArrayList();
4051248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai        if (chips == null) {
4061248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai            return results;
4071248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai        }
4081248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai
4091248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai        for (DrawableRecipientChip c : chips) {
4101248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai            results.add(c.getEntry());
4111248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai        }
4121248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai
4131248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai        return results;
4141248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai    }
4151248e53bed1011088489b0d9adf17f5a05be8d78Ken Thai
416fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    @Override
417fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    public void onSelectionChanged(int start, int end) {
418fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // When selection changes, see if it is inside the chips area.
419fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // If so, move the cursor back after the chips again.
420194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip last = getLastChip();
421f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (last != null && start < getSpannable().getSpanEnd(last)) {
422aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            // Grab the last chip and set the cursor to after it.
423aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
42405dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
42505dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        super.onSelectionChanged(start, end);
42605dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    }
42705dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira
428dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    @Override
429dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    public void onRestoreInstanceState(Parcelable state) {
430dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        if (!TextUtils.isEmpty(getText())) {
431dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            super.onRestoreInstanceState(null);
432dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        } else {
433dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            super.onRestoreInstanceState(state);
434dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        }
435dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    }
436dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira
437aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    @Override
438daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira    public Parcelable onSaveInstanceState() {
439daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
440daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        clearSelectedChip();
441daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        return super.onSaveInstanceState();
442daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira    }
443daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira
44402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    /**
44502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
44602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
44702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * not already editable. Commas are excluded as they are added automatically
44802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * by the view.
44902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     */
45002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    @Override
45102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    public void append(CharSequence text, int start, int end) {
452e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        // We don't care about watching text changes while appending.
453e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        if (mTextWatcher != null) {
454e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            removeTextChangedListener(mTextWatcher);
455e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        }
45602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        super.append(text, start, end);
45702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
4580124468e712b4098ea240b8d45f84e52826b293dmindyp            String displayString = text.toString();
45903e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy
46003e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy            if (!displayString.trim().endsWith(String.valueOf(COMMIT_CHAR_COMMA))) {
46103e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                // We have no separator, so we should add it
46203e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                super.append(SEPARATOR, 0, SEPARATOR.length());
46303e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                displayString += SEPARATOR;
4640124468e712b4098ea240b8d45f84e52826b293dmindyp            }
46503e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy
46620d7af71188a4a2d94c8d9edd7eff7879d6df4c4mindyp            if (!TextUtils.isEmpty(displayString)
46702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
46802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                mPendingChipsCount++;
46903e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                mPendingChips.add(displayString);
47002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira            }
47102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        }
472abb864d610bdd171d6b0dfd2e83648952155e6ebmindyp        // Put a message on the queue to make sure we ALWAYS handle pending
473abb864d610bdd171d6b0dfd2e83648952155e6ebmindyp        // chips.
4742cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        if (mPendingChipsCount > 0) {
4752cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            postHandlePendingChips();
4762cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
4774e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        mHandler.post(mAddTextWatcher);
47802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    }
47902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
48005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    @Override
48105dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
482d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
48305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        if (!hasFocus) {
48412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            shrink();
485fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        } else {
48612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            expand();
487fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        }
4889159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
4899159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
490093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    private int getExcessTopPadding() {
491093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (sExcessTopPadding == -1) {
492e7f7a7d521b6b369035370b6c9241d2c6e313b44mindyp            sExcessTopPadding = (int) (mChipHeight + mLineSpacingExtra);
493093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        }
494093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        return sExcessTopPadding;
495093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    }
496093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
4976b7110f320c978c368c28bdb06212c6a6df12f1fAlice Yang    @Override
498093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
499093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        super.setAdapter(adapter);
500b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        BaseRecipientAdapter baseAdapter = (BaseRecipientAdapter) adapter;
501b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        baseAdapter.registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
502b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            @Override
503b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            public void onChanged(List<RecipientEntry> entries) {
504b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                // Scroll the chips field to the top of the screen so
505b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                // that the user can see as many results as possible.
506b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                if (entries != null && entries.size() > 0) {
507b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                    scrollBottomIntoView();
508b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                }
509b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            }
510b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        });
511b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        baseAdapter.setDropdownChipLayouter(mDropdownChipLayouter);
512093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    }
513093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
51453e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin    protected void scrollBottomIntoView() {
515093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (mScrollView != null && mShouldShrink) {
516093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int[] location = new int[2];
517093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            getLocationOnScreen(location);
518093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int height = getHeight();
519093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int currentPos = location[1] + height;
520093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            // Desired position shows at least 1 line of chips below the action
521093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            // bar. We add excess padding to make sure this is always below other
522093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            // content.
523093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int desiredPos = (int) mChipHeight + mActionBarHeight + getExcessTopPadding();
524093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            if (currentPos > desiredPos) {
525093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp                mScrollView.scrollBy(0, currentPos - desiredPos);
526093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            }
527093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        }
528093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    }
529093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
53053e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin    protected ScrollView getScrollView() {
5317a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy        return mScrollView;
53253e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin    }
53353e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin
534d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    @Override
535d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    public void performValidation() {
536d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira        // Do nothing. Chips handles its own validation.
537d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    }
538d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira
53912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private void shrink() {
5405c125afa54288595524c85182031421cbad08ac3Mindy Pereira        if (mTokenizer == null) {
5415c125afa54288595524c85182031421cbad08ac3Mindy Pereira            return;
5425c125afa54288595524c85182031421cbad08ac3Mindy Pereira        }
5435e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        long contactId = mSelectedChip != null ? mSelectedChip.getEntry().getContactId() : -1;
5445e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (mSelectedChip != null && contactId != RecipientEntry.INVALID_CONTACT
5455e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                && (!isPhoneQuery() && contactId != RecipientEntry.GENERATED_CONTACT)) {
54612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            clearSelectedChip();
54712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        } else {
548d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            if (getWidth() <= 0) {
549d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
550d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // and there is no reason to attempt to commit chips yet.
551d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // This focus lost must be the result of an orientation change
552d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // or an initial rendering.
553d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // Re-post the shrink for later.
554d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
555d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                mHandler.post(mDelayedShrink);
556d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                return;
557d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            }
558e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            // Reset any pending chips as they would have been handled
559e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            // when the field lost focus.
560e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            if (mPendingChipsCount > 0) {
5612cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                postHandlePendingChips();
562e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            } else {
563e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                Editable editable = getText();
564e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                int end = getSelectionEnd();
565e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
566194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] chips =
567194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        getSpannable().getSpans(start, end, DrawableRecipientChip.class);
568e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                if ((chips == null || chips.length == 0)) {
569d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    Editable text = getText();
570d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
571d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    // This token was already tokenized, so skip past the ending token.
572448e90b97a4df8102d6e1d2039274d9ea188dff9Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
5734f82d888c680a61b95373740ce68bfb48a242617mindyp                        whatEnd = movePastTerminators(whatEnd);
574d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    }
575e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    // In the middle of chip; treat this as an edit
576e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    // and commit the whole token.
577d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    int selEnd = getSelectionEnd();
5786337231a660b717cd6f5c40d524f4aabfcc865b0Mindy Pereira                    if (whatEnd != selEnd) {
579e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                        handleEdit(start, whatEnd);
580e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    } else {
581e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                        commitChip(start, end, editable);
582e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    }
583e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                }
584a74f40dc1073117349d1d39b7c8396a39d24f57fMindy Pereira            }
585e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            mHandler.post(mAddTextWatcher);
58612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
5873bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        createMoreChip();
58812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
58912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
59012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private void expand() {
591374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp        if (mShouldShrink) {
592374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp            setMaxLines(Integer.MAX_VALUE);
593374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp        }
59412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        removeMoreChip();
59512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        setCursorVisible(true);
59612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        Editable text = getText();
59712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
5981852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
5991852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // has expanded the field.
6001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
6011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            new RecipientReplacementTask().execute();
6021852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            mTemporaryRecipients = null;
6031852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
60412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
60512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
6062bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
6076e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira        paint.setTextSize(mChipFontSize);
60802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
60902a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
61002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        }
61102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
61202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                TextUtils.TruncateAt.END);
6132bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
614cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
615b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
616b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Creates a bitmap of the given contact on a selected chip.
617b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     *
618b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param contact The recipient entry to pull data from.
619b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param paint The paint to use to draw the bitmap.
620b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
621f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint) {
622b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        paint.setColor(sSelectedTextColor);
62335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        final ChipBitmapContainer bitmapContainer = createChipBitmap(contact, paint,
62435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                mChipBackgroundPressed);
62535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
62635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        if (bitmapContainer.loadIcon) {
62735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            if (mDisableDelete) {
62835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                // Show the avatar instead if we don't want to delete
62935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                loadAvatarIcon(contact, bitmapContainer, paint);
63035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            } else {
63135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                drawIcon(bitmapContainer, ((BitmapDrawable) mChipDelete).getBitmap(), paint);
63235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            }
6332bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
63435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
63535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        return bitmapContainer.bitmap;
6362bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
637cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
638b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
639b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Creates a bitmap of the given contact on a selected chip.
640b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     *
641b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param contact The recipient entry to pull data from.
642b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param paint The paint to use to draw the bitmap.
643b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
6441db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint) {
645b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Drawable background = getChipBackground(contact);
646b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        paint.setColor(getContext().getResources().getColor(android.R.color.black));
64735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        ChipBitmapContainer bitmapContainer = createChipBitmap(contact, paint, background);
64835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
64935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        if (bitmapContainer.loadIcon) {
65035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            loadAvatarIcon(contact, bitmapContainer, paint);
65135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        }
65235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        return bitmapContainer.bitmap;
653b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
654b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
65535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao    private ChipBitmapContainer createChipBitmap(RecipientEntry contact, TextPaint paint,
65635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            Drawable background) {
65735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        final ChipBitmapContainer result = new ChipBitmapContainer();
65835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
659b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (background == null) {
66035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            Log.w(TAG, "Unable to draw a background for the chip as it was never set");
66135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            result.bitmap = Bitmap.createBitmap(
662c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                    (int) mChipHeight * 2, (int) mChipHeight, Bitmap.Config.ARGB_8888);
66335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            result.loadIcon = false;
66435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            return result;
665b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        }
666b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
667b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Rect backgroundPadding = new Rect();
668b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        background.getPadding(backgroundPadding);
669b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
670cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
671cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
672cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // on the sides.
6736e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira        int height = (int) mChipHeight;
674b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Since the icon is a square, it's width is equal to the maximum height it can be inside
675b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // the chip.
676b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        int iconWidth = height - backgroundPadding.top - backgroundPadding.bottom;
6776ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        float[] widths = new float[1];
6786ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        paint.getTextWidths(" ", widths);
679aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
6809b398e3019d1e12c661647a277a338975a50d952Kevin Lin                calculateAvailableWidth() - iconWidth - widths[0] - backgroundPadding.left
6819b398e3019d1e12c661647a277a338975a50d952Kevin Lin                    - backgroundPadding.right);;
682b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        int textWidth = (int) paint.measureText(ellipsizedText, 0, ellipsizedText.length());
683b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
6849d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
6859d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira        // tap a chip without difficulty.
686b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        int width = Math.max(iconWidth * 2, textWidth + (mChipPadding * 2) + iconWidth
687b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                + backgroundPadding.left + backgroundPadding.right);
688cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
689cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Create the background of the chip.
69035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        result.bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
69135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        final Canvas canvas = new Canvas(result.bitmap);
6926b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira
693b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Draw the background drawable
694b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        background.setBounds(0, 0, width, height);
695b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        background.draw(canvas);
696b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Draw the text vertically aligned
697c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        int textX = shouldPositionAvatarOnRight() ?
698b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                mChipPadding + backgroundPadding.left :
699b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                width - backgroundPadding.right - mChipPadding - textWidth;
700b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
701b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                textX, getTextYOffset(ellipsizedText.toString(), paint, height), paint);
70235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
70335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        // Set the variables that are needed to draw the icon bitmap once it's loaded
70435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        int iconX = shouldPositionAvatarOnRight() ? width - backgroundPadding.right - iconWidth :
70535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                backgroundPadding.left;
70635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        result.left = iconX;
70735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        result.top = backgroundPadding.top;
70835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        result.right = iconX + iconWidth;
70935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        result.bottom = height - backgroundPadding.bottom;
71035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
71135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        return result;
71235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao    }
71335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
71435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao    /**
71535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao     * Helper function that draws the loaded icon bitmap into the chips bitmap
71635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao     */
71735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao    private void drawIcon(ChipBitmapContainer bitMapResult, Bitmap icon, Paint paint) {
71835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        final Canvas canvas = new Canvas(bitMapResult.bitmap);
71935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        final RectF src = new RectF(0, 0, icon.getWidth(), icon.getHeight());
72035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        final RectF dst = new RectF(bitMapResult.left, bitMapResult.top, bitMapResult.right,
72135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                bitMapResult.bottom);
72235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        drawIconOnCanvas(icon, canvas, paint, src, dst);
7232bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
724cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
725aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /**
726c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * Returns true if the avatar should be positioned at the right edge of the chip.
727c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * Takes into account both the set avatar position (start or end) as well as whether
728c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * the layout direction is LTR or RTL.
729c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     */
730c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    private boolean shouldPositionAvatarOnRight() {
731c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ?
732c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                getLayoutDirection() == LAYOUT_DIRECTION_RTL : false;
733c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        final boolean assignedPosition = mAvatarPosition == AVATAR_POSITION_END;
734c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        // If in Rtl mode, the position should be flipped.
735c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        return isRtl ? !assignedPosition : assignedPosition;
736c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    }
737c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein
738c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    /**
739b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Returns the avatar icon to use for this recipient entry. Returns null if we don't want to
740b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * draw an icon for this recipient.
741b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
74235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao    private void loadAvatarIcon(final RecipientEntry contact,
74335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            final ChipBitmapContainer bitmapContainer, final Paint paint) {
744b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Don't draw photos for recipients that have been typed in OR generated on the fly.
745b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        long contactId = contact.getContactId();
746b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        boolean drawPhotos = isPhoneQuery() ?
747b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                contactId != RecipientEntry.INVALID_CONTACT
748b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                : (contactId != RecipientEntry.INVALID_CONTACT
749b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                        && (contactId != RecipientEntry.GENERATED_CONTACT &&
750b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                                !TextUtils.isEmpty(contact.getDisplayName())));
751b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
752b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (drawPhotos) {
75335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            final byte[] origPhotoBytes = contact.getPhotoBytes();
754b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            // There may not be a photo yet if anything but the first contact address
755b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            // was selected.
75635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao            if (origPhotoBytes == null && (contact.getPhotoThumbnailUri() != null ||
7578af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    getAdapter().ignoreNullThumbnailUri())) {
758b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                // TODO: cache this in the recipient entry?
75935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                getAdapter().fetchPhoto(contact, new PhotoManager.PhotoManagerCallback() {
76035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                        @Override
76135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                        public void onPhotoBytesAsynchronouslyPopulated() {
76235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                            final byte[] loadedPhotoBytes = contact.getPhotoBytes();
76335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                            final Bitmap icon;
76435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                            if (loadedPhotoBytes != null) {
76535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                                icon = BitmapFactory.decodeByteArray(loadedPhotoBytes, 0,
76635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                                        loadedPhotoBytes.length);
76735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                            } else {
76835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                                // TODO: can the scaled down default photo be cached?
76935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                                icon = mDefaultContactPhoto;
77035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                            }
77135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                            // This is called on the main thread so we can draw the icon here
77235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                            drawIcon(bitmapContainer, icon, paint);
77335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                        }
77435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                });
775b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            } else {
77635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                final Bitmap icon = BitmapFactory.decodeByteArray(origPhotoBytes, 0,
77735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                        origPhotoBytes.length);
77835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao                drawIcon(bitmapContainer, icon, paint);
779b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            }
780b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        }
781b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
782b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
783b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
784aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira     * Get the background drawable for a RecipientChip.
785aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira     */
786aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
787f30a42800318f6790d55421f8f6980eb38db4d3cmindyp    /* package */Drawable getChipBackground(RecipientEntry contact) {
788f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return contact.isValid() ? mChipBackground : mInvalidChipBackground;
789aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    }
790aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
791b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
792b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Given a height, returns a Y offset that will draw the text in the middle of the height.
793b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
794b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    protected float getTextYOffset(String text, TextPaint paint, int height) {
7951d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        Rect bounds = new Rect();
796e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        paint.getTextBounds(text, 0, text.length(), bounds);
7978ff03734c31831d3d60b0cb2707c006dbf64d330Mindy Pereira        int textHeight = bounds.bottom - bounds.top ;
7988ff03734c31831d3d60b0cb2707c006dbf64d330Mindy Pereira        return height - ((height - textHeight) / 2) - (int)paint.descent();
7991d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira    }
8001d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira
801b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
802b8985b7b595e38518d2a0657b89ff47bd34862abScott Kennedy     * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination
803b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * rectangle of the canvas.
804b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
805b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    protected void drawIconOnCanvas(Bitmap icon, Canvas canvas, Paint paint, RectF src, RectF dst) {
806b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Matrix matrix = new Matrix();
807b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
808b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        canvas.drawBitmap(icon, matrix, paint);
809b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
810b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
8111db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein    private DrawableRecipientChip constructChipSpan(RecipientEntry contact, boolean pressed)
8121db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein            throws NullPointerException {
8132bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        if (mChipBackground == null) {
8142bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            throw new NullPointerException(
8152bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
816cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
81702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
8182bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        TextPaint paint = getPaint();
8192bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        float defaultSize = paint.getTextSize();
820e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        int defaultColor = paint.getColor();
8212bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
8222bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        Bitmap tmpBitmap;
8232bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        if (pressed) {
824f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy            tmpBitmap = createSelectedChip(contact, paint);
825cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
8262bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        } else {
8271db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein            tmpBitmap = createUnselectedChip(contact, paint);
8282bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
829cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
830cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
831cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
8322bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
8339b398e3019d1e12c661647a277a338975a50d952Kevin Lin        DrawableRecipientChip recipientChip =
8349b398e3019d1e12c661647a277a338975a50d952Kevin Lin                new VisibleRecipientChip(result, contact, getImageSpanAlignment());
835cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Return text to the original size.
836cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        paint.setTextSize(defaultSize);
837e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        paint.setColor(defaultColor);
838cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return recipientChip;
8399159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
8409159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
8419b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private int getImageSpanAlignment() {
8429b398e3019d1e12c661647a277a338975a50d952Kevin Lin        switch (mImageSpanAlignment) {
8439b398e3019d1e12c661647a277a338975a50d952Kevin Lin            case IMAGE_SPAN_ALIGNMENT_BASELINE:
8449b398e3019d1e12c661647a277a338975a50d952Kevin Lin                return ImageSpan.ALIGN_BASELINE;
8459b398e3019d1e12c661647a277a338975a50d952Kevin Lin            case IMAGE_SPAN_ALIGNMENT_BOTTOM:
8469b398e3019d1e12c661647a277a338975a50d952Kevin Lin                return ImageSpan.ALIGN_BOTTOM;
8479b398e3019d1e12c661647a277a338975a50d952Kevin Lin            default:
8489b398e3019d1e12c661647a277a338975a50d952Kevin Lin                return ImageSpan.ALIGN_BOTTOM;
8499b398e3019d1e12c661647a277a338975a50d952Kevin Lin        }
8509b398e3019d1e12c661647a277a338975a50d952Kevin Lin    }
8519b398e3019d1e12c661647a277a338975a50d952Kevin Lin
852a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
853342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
854342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * 1) which line the chip appears on
85521625f86828b6bbfc5e87796564eaca5127155feMindy Pereira     * 2) the height of a chip
85621625f86828b6bbfc5e87796564eaca5127155feMindy Pereira     * 3) padding built into the edit text view
857a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
85881fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira    private int calculateOffsetFromBottom(int line) {
85921625f86828b6bbfc5e87796564eaca5127155feMindy Pereira        // Line offsets start at zero.
860c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        int actualLine = getLineCount() - (line + 1);
8611d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
8621d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira                + getDropDownVerticalOffset();
863f621a601e1f966c89b7aadbcca384021e14d668dMindy Pereira    }
864f621a601e1f966c89b7aadbcca384021e14d668dMindy Pereira
865a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
866a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
867a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
868a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * that will be added to the chip.
869a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
870f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private float calculateAvailableWidth() {
8712bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
8729159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
8739159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
87452c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira
87522faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira    private void setChipDimensions(Context context, AttributeSet attrs) {
8765da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecipientEditTextView, 0,
8775da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                0);
87852c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira        Resources r = getContext().getResources();
8798c474ec629cca3cf4bf2c867f37513ae35e3ff72mindyp
88022faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        mChipBackground = a.getDrawable(R.styleable.RecipientEditTextView_chipBackground);
88122faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        if (mChipBackground == null) {
88222faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira            mChipBackground = r.getDrawable(R.drawable.chip_background);
88322faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        }
8845da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipBackgroundPressed = a
8855da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                .getDrawable(R.styleable.RecipientEditTextView_chipBackgroundPressed);
8865da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipBackgroundPressed == null) {
8875da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipBackgroundPressed = r.getDrawable(R.drawable.chip_background_selected);
8885da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8895da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipDelete = a.getDrawable(R.styleable.RecipientEditTextView_chipDelete);
8905da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipDelete == null) {
8915da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipDelete = r.getDrawable(R.drawable.chip_delete);
8925da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8935da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipPadding = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipPadding, -1);
8945da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipPadding == -1) {
8955da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
8965da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8975da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
8985da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
8995da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
90052c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.more_item, null);
9015da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
9025da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipHeight = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipHeight, -1);
9035da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipHeight == -1) {
9045da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipHeight = r.getDimension(R.dimen.chip_height);
9055da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
9065da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipFontSize = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipFontSize, -1);
9075da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipFontSize == -1) {
9085da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipFontSize = r.getDimension(R.dimen.chip_text_size);
9095da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
9105da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mInvalidChipBackground = a
9115da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                .getDrawable(R.styleable.RecipientEditTextView_invalidChipBackground);
9125da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mInvalidChipBackground == null) {
9135da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
9145da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
915b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        mAvatarPosition = a.getInt(R.styleable.RecipientEditTextView_avatarPosition, 0);
9169b398e3019d1e12c661647a277a338975a50d952Kevin Lin        mImageSpanAlignment = a.getInt(R.styleable.RecipientEditTextView_imageSpanAlignment, 0);
917b8985b7b595e38518d2a0657b89ff47bd34862abScott Kennedy        mDisableDelete = a.getBoolean(R.styleable.RecipientEditTextView_disableDelete, false);
918b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
919f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        mLineSpacingExtra =  r.getDimension(R.dimen.line_spacing_extra);
920f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        mMaxLines = r.getInteger(R.integer.chips_max_lines);
921093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        TypedValue tv = new TypedValue();
922093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
923093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
924093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp                    .getDisplayMetrics());
925093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        }
926b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
927ef79800ff65490ec91ce77f55c4ade142d1cc4b2Mindy Pereira        a.recycle();
92852c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira    }
92952c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira
930d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
931d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
932d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        mMoreItem = moreItem;
933d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    }
934d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
93597cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
93697cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
93797cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
93897cb25912dab282cf732757f68b0405ed005f00bMindy Pereira        mChipBackground = chipBackground;
93997cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    }
94097cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
94197cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
94297cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    /* package */ void setChipHeight(int height) {
94397cb25912dab282cf732757f68b0405ed005f00bMindy Pereira        mChipHeight = height;
94497cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    }
94597cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
946b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    public float getChipHeight() {
947b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return mChipHeight;
948b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
949b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
950076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    /**
951076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * Set whether to shrink the recipients field such that at most
952076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * one line of recipients chips are shown when the field loses
953076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * focus. By default, the number of displayed recipients will be
954076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
955076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * @param shrink
956076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     */
957076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
958076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        mShouldShrink = shrink;
959076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    }
960076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira
961cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
96202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
96302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
964db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (width != 0 && height != 0) {
965db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            if (mPendingChipsCount > 0) {
966db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                postHandlePendingChips();
967db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            } else {
968db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                checkChipWidths();
969db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            }
9708005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira        }
9711852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // Try to find the scroll view parent, if it exists.
972aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
973c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            ViewParent parent = getParent();
974c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
975c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                parent = parent.getParent();
976c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            }
977c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            if (parent != null) {
978c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                mScrollView = (ScrollView) parent;
979c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            }
980aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            mTriedGettingScrollView = true;
981c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        }
98202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    }
98302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
9842cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    private void postHandlePendingChips() {
9852cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
9862cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        mHandler.post(mHandlePendingChips);
9872cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    }
9882cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
989db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira    private void checkChipWidths() {
990db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        // Check the widths of the associated chips.
991194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips = getSortedRecipients();
992db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (chips != null) {
993db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            Rect bounds;
994194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            for (DrawableRecipientChip chip : chips) {
995f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                bounds = chip.getBounds();
996c61c13ffde5d4224ee861f7e7fe3ea0900128a7cKevin Lin                if (getWidth() > 0 && bounds.right - bounds.left >
997c61c13ffde5d4224ee861f7e7fe3ea0900128a7cKevin Lin                        getWidth() - getPaddingLeft() - getPaddingRight()) {
998db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                    // Need to redraw that chip.
999db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                    replaceChip(chip, chip.getEntry());
1000db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                }
1001db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            }
10022cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
1003db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira    }
1004db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira
1005f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1006f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ void handlePendingChips() {
1007f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (getViewWidth() <= 0) {
10088005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // The widget has not been sized yet.
10098005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // This will be called as a result of onSizeChanged
10108005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // at a later point.
10118005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            return;
10128005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira        }
1013db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (mPendingChipsCount <= 0) {
1014db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            return;
1015db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        }
1016db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira
10172cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        synchronized (mPendingChips) {
10182cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            Editable editable = getText();
10192cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            // Tokenize!
1020f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
1021f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                for (int i = 0; i < mPendingChips.size(); i++) {
1022f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    String current = mPendingChips.get(i);
1023f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    int tokenStart = editable.toString().indexOf(current);
1024488718e8f464d8554dd1cb00395fdfc7871f0fc8mindyp                    // Always leave a space at the end between tokens.
1025488718e8f464d8554dd1cb00395fdfc7871f0fc8mindyp                    int tokenEnd = tokenStart + current.length() - 1;
1026f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    if (tokenStart >= 0) {
1027f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        // When we have a valid token, include it with the token
1028f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        // to the left.
1029f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        if (tokenEnd < editable.length() - 2
1030f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                                && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
1031f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                            tokenEnd++;
1032f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        }
1033f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                        createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT
1034f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                                || !mShouldShrink);
10352cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    }
1036f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    mPendingChipsCount--;
10373bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                }
1038f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                sanitizeEnd();
1039f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            } else {
1040f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                mNoChips = true;
10413bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
1042f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1043dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
10442cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
10452cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
10462cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    new RecipientReplacementTask().execute();
10472cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    mTemporaryRecipients = null;
10482cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                } else {
10492cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    // Create the "more" chip
10502cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
1051194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    mIndividualReplacements.execute(new ArrayList<DrawableRecipientChip>(
10522cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
10535e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                    if (mTemporaryRecipients.size() > CHIP_LIMIT) {
1054194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        mTemporaryRecipients = new ArrayList<DrawableRecipientChip>(
10555e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                                mTemporaryRecipients.subList(CHIP_LIMIT,
10565e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                                        mTemporaryRecipients.size()));
10575e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                    } else {
10585e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                        mTemporaryRecipients = null;
10595e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                    }
10602cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    createMoreChip();
10612cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                }
10622cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            } else {
10632cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                // There are too many recipients to look up, so just fall back
1064dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                // to showing addresses for all of them.
10652cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                mTemporaryRecipients = null;
10661852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                createMoreChip();
10671852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
10682cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            mPendingChipsCount = 0;
10692cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            mPendingChips.clear();
10703bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
10713bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
10723bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
1073f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1074f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ int getViewWidth() {
1075f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return getWidth();
1076f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1077f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
10783bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    /**
10793bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * Remove any characters after the last valid chip.
10803bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     */
1081aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
1082aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ void sanitizeEnd() {
10833c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        // Don't sanitize while we are waiting for pending chips to complete.
10843c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        if (mPendingChipsCount > 0) {
10853c42baf5b81810bce77e776963f20c960865e85bMindy Pereira            return;
10863c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        }
10873bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
1088194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips = getSortedRecipients();
1089f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        Spannable spannable = getSpannable();
10903bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        if (chips != null && chips.length > 0) {
10913bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            int end;
1092dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            mMoreChip = getMoreChip();
10933bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            if (mMoreChip != null) {
1094f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                end = spannable.getSpanEnd(mMoreChip);
10953bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            } else {
1096f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                end = getSpannable().getSpanEnd(getLastChip());
10973bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
10983bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            Editable editable = getText();
10993bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            int length = editable.length();
11003bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            if (length > end) {
11013bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                // See what characters occur after that and eliminate them.
11023bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
11033bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
11043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                            + editable);
11053bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                }
11063bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                editable.delete(end + 1, length);
11073bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
11083bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
11093bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
11103bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
11113bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    /**
11123bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
11133bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
11143bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     */
111520c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    // VisibleForTesting
111620c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    void createReplacementChip(int tokenStart, int tokenEnd, Editable editable,
1117f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            boolean visible) {
111832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
111932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // There is already a chip present at this location.
112032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // Don't recreate it.
112132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            return;
112232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        }
11237ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
112420c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy        final String trimmedToken = token.trim();
112520c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy        int commitCharIndex = trimmedToken.lastIndexOf(COMMIT_CHAR_COMMA);
112641137c249b5fe547a3631d9c6db57ff980022affTom Taylor        if (commitCharIndex != -1 && commitCharIndex == trimmedToken.length() - 1) {
112720c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy            token = trimmedToken.substring(0, trimmedToken.length() - 1);
11283bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
11293bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
1130ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        if (entry != null) {
1131194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip = null;
1132ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            try {
1133f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (!mNoChips) {
1134f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    chip = visible ?
11351db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein                            constructChipSpan(entry, false) : new InvisibleRecipientChip(entry);
1136f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
1137ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            } catch (NullPointerException e) {
1138ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1139ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
1140cb76b4d2bcb7b72b9505a620ca71f753506e48b9mindyp            editable.setSpan(chip, tokenStart, tokenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1141ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            // Add this chip to the list of entries "to replace"
1142ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            if (chip != null) {
1143dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                if (mTemporaryRecipients == null) {
1144194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    mTemporaryRecipients = new ArrayList<DrawableRecipientChip>();
1145dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                }
11460613ff859a6b44685af62821eac369597cf69b26Scott Kennedy                chip.setOriginalText(token);
1147ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                mTemporaryRecipients.add(chip);
1148ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
11491852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
11503bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
11513bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
1152abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor    private static boolean isPhoneNumber(String number) {
1153abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        // TODO: replace this function with libphonenumber's isPossibleNumber (see
1154abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        // PhoneNumberUtil). One complication is that it requires the sender's region which
1155abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        // comes from the CurrentCountryIso. For now, let's just do this simple match.
1156abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        if (TextUtils.isEmpty(number)) {
1157abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            return false;
1158abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        }
1159abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor
11604491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor        Matcher match = PHONE_PATTERN.matcher(number);
1161abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        return match.matches();
1162abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor    }
1163abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor
1164983c99b9e00913e8d2a5642cd05ccb9a81570be7Scott Kennedy    // VisibleForTesting
1165983c99b9e00913e8d2a5642cd05ccb9a81570be7Scott Kennedy    RecipientEntry createTokenizedEntry(final String token) {
1166ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        if (TextUtils.isEmpty(token)) {
1167ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            return null;
1168ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        }
11695e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (isPhoneQuery() && isPhoneNumber(token)) {
11700ba9133c904b8c35af8209a54604331cd671bc1fTom Taylor            return RecipientEntry.constructFakePhoneEntry(token, true);
11715e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        }
11723bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
117301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        String display = null;
1174f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        boolean isValid = isValid(token);
1175f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        if (isValid && tokens != null && tokens.length > 0) {
1176ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira            // If we can get a name from tokenizing, then generate an entry from
1177ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira            // this.
117801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            display = tokens[0].getName();
117901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (!TextUtils.isEmpty(display)) {
1180f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                return RecipientEntry.constructGeneratedEntry(display, tokens[0].getAddress(),
1181f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                        isValid);
1182401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira            } else {
1183401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                display = tokens[0].getAddress();
1184401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                if (!TextUtils.isEmpty(display)) {
1185f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    return RecipientEntry.constructFakeEntry(display, isValid);
1186401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                }
118701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
118801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        }
11894e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        // Unable to validate the token or to create a valid token from it.
11904e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        // Just create a chip the user can edit.
1191dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira        String validatedToken = null;
1192f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        if (mValidator != null && !isValid) {
11934e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            // Try fixing up the entry using the validator.
1194dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira            validatedToken = mValidator.fixText(token).toString();
1195dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
1196dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                if (validatedToken.contains(token)) {
1197f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // protect against the case of a validator with a null
1198f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // domain,
1199dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // which doesn't add a domain to the token
1200dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
1201dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    if (tokenized.length > 0) {
1202dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                        validatedToken = tokenized[0].getAddress();
1203f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                        isValid = true;
1204dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    }
1205dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                } else {
1206f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // We ran into a case where the token was invalid and
1207f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // removed
1208f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // by the validator. In this case, just use the original
1209f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // token
1210dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // and let the user sort out the error chip.
1211dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    validatedToken = null;
1212f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    isValid = false;
1213184d3773dc096c7a6a83f7aeda042fa8346c2024Erik                }
1214ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
12154e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        }
1216ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
1217f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return RecipientEntry.constructFakeEntry(
1218f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                !TextUtils.isEmpty(validatedToken) ? validatedToken : token, isValid);
121901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
122001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
12214e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira    private boolean isValid(String text) {
12224e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
12234e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira    }
12244e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira
1225f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private static String tokenizeAddress(String destination) {
122601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
122701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        if (tokens != null && tokens.length > 0) {
122801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            return tokens[0].getAddress();
12293bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
123001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        return destination;
12313bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
12323bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
123302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    @Override
1234cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
1235cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        mTokenizer = tokenizer;
1236cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        super.setTokenizer(mTokenizer);
1237cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1238cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1239a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    @Override
1240a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    public void setValidator(Validator validator) {
1241a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira        mValidator = validator;
1242a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira        super.setValidator(validator);
1243a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    }
1244a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira
1245a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1246a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
1247a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * we override onItemClickListener so we can get all the associated
1248a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * contact information including display text, address, and id.
1249a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1250cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1251cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    protected void replaceText(CharSequence text) {
1252cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return;
1253cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1254cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1255a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1256a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
1257a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1258cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
12594c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
126003cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK && mSelectedChip != null) {
12614c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira            clearSelectedChip();
126203cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira            return true;
12634c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira        }
12644c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira        return super.onKeyPreIme(keyCode, event);
12654c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    }
12664c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira
1267a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1268a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Monitor key presses in this view to see if the user types
1269a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
1270a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the user has entered text that has contact matches and types
1271a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
1272a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the user has entered text that has no contact matches and types
1273a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * a commit key, then create a chip from the text they have entered.
1274a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
12754c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    @Override
1276cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
1277cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        switch (keyCode) {
12780f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            case KeyEvent.KEYCODE_TAB:
12790f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                if (event.hasNoModifiers()) {
12800f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    if (mSelectedChip != null) {
12810f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        clearSelectedChip();
12820f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    } else {
12830f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        commitDefault();
12840f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    }
12850f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                }
128603cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira                break;
1287cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1288cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return super.onKeyUp(keyCode, event);
1289cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1290cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
12910f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira    private boolean focusNext() {
12920f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
12930f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        if (next != null) {
12940f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            next.requestFocus();
12950f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            return true;
12960f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        }
12970f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        return false;
12980f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira    }
12990f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira
1300342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira    /**
1301342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
1302e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy     * default is the selected item (if one is selected), or the first item, in the popup
1303e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy     * suggestions list. Otherwise, it is whatever the user had typed in. End represents where the
1304e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy     * tokenizer should search for a token to turn into a chip.
1305342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * @return If a chip was created from a real contact.
1306342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     */
1307a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    private boolean commitDefault() {
130860d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        // If there is no tokenizer, don't try to commit.
130960d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        if (mTokenizer == null) {
131060d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira            return false;
131160d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        }
131212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        Editable editable = getText();
1313342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira        int end = getSelectionEnd();
131412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
13152b4ffc53a3b51631cb6aabf535986a9344ee6cbbMindy Pereira
131679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        if (shouldCreateChip(start, end)) {
131779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
131879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // In the middle of chip; treat this as an edit
131979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // and commit the whole token.
13204f82d888c680a61b95373740ce68bfb48a242617mindyp            whatEnd = movePastTerminators(whatEnd);
132179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (whatEnd != getSelectionEnd()) {
132279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                handleEdit(start, whatEnd);
132379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                return true;
132412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            }
132579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            return commitChip(start, end , editable);
132679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
132779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        return false;
132879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
132979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
133079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private void commitByCharacter() {
133160d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        // We can't possibly commit by character if we can't tokenize.
133260d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        if (mTokenizer == null) {
133360d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira            return;
133460d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        }
133579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        Editable editable = getText();
133679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        int end = getSelectionEnd();
133779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
133879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        if (shouldCreateChip(start, end)) {
133979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            commitChip(start, end, editable);
134012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
1341ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira        setSelection(getText().length());
134279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
134312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
134479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
134520c9d620e79ae28994856541761a951074551518Mindy Pereira        ListAdapter adapter = getAdapter();
134620c9d620e79ae28994856541761a951074551518Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
1347c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor                && end == getSelectionEnd() && !isPhoneQuery()) {
1348e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy            // let's choose the selected or first entry if only the input text is NOT an email
1349e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy            // address so we won't try to replace the user's potentially correct but
1350e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy            // new/unencountered email input
1351b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao            if (!isValidEmailAddress(editable.toString().substring(start, end).trim())) {
1352e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy                final int selectedPosition = getListSelection();
1353e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy                if (selectedPosition == -1) {
1354e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy                    // Nothing is selected; use the first item
1355e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy                    submitItemAtPosition(0);
1356e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy                } else {
1357e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy                    submitItemAtPosition(selectedPosition);
1358e4945e96e5b67a1a5b8fd165debc616a351fda17Scott Kennedy                }
1359b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao            }
136079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            dismissDropDown();
136179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            return true;
136279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        } else {
136379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
136420c9d620e79ae28994856541761a951074551518Mindy Pereira            if (editable.length() > tokenEnd + 1) {
136520c9d620e79ae28994856541761a951074551518Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
136620c9d620e79ae28994856541761a951074551518Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
136720c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenEnd++;
136820c9d620e79ae28994856541761a951074551518Mindy Pereira                }
1369d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            }
137062397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
137179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            clearComposingText();
137279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
137301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1374ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                if (entry != null) {
1375ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1376ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                    CharSequence chipText = createChip(entry, false);
1377ee6d83fe1da297b2f9af0fb221be376fdc816830Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
1378f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira                        editable.replace(start, end, chipText);
1379f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira                    }
1380ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                }
138120c9d620e79ae28994856541761a951074551518Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
138220c9d620e79ae28994856541761a951074551518Mindy Pereira                // just committed.
138320c9d620e79ae28994856541761a951074551518Mindy Pereira                // For paste, it may not be as there are possibly multiple
138420c9d620e79ae28994856541761a951074551518Mindy Pereira                // tokens being added.
138520c9d620e79ae28994856541761a951074551518Mindy Pereira                if (end == getSelectionEnd()) {
138620c9d620e79ae28994856541761a951074551518Mindy Pereira                    dismissDropDown();
138720c9d620e79ae28994856541761a951074551518Mindy Pereira                }
13885df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                sanitizeBetween();
138912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                return true;
139005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            }
139105dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
139212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
139305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    }
139405dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira
139501162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
139601162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    /* package */ void sanitizeBetween() {
13973c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        // Don't sanitize while we are waiting for content to chipify.
13983c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        if (mPendingChipsCount > 0) {
13993c42baf5b81810bce77e776963f20c960865e85bMindy Pereira            return;
14003c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        }
14015df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        // Find the last chip.
1402194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] recips = getSortedRecipients();
14035df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        if (recips != null && recips.length > 0) {
1404194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip last = recips[recips.length - 1];
1405194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip beforeLast = null;
14065df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            if (recips.length > 1) {
14075df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                beforeLast = recips[recips.length - 2];
14085df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
14095df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            int startLooking = 0;
14105df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            int end = getSpannable().getSpanStart(last);
14115df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            if (beforeLast != null) {
14125df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1413399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                Editable text = getText();
141401162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1415399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                    // There is nothing after this chip.
1416399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                    return;
1417399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                }
1418399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
14195df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                    startLooking++;
14205df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                }
14215df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
1422e13775cc6e9342e42db0b853cc42dbfda6d1365fMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking < end) {
14235df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                getText().delete(startLooking, end);
14245df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
14255df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        }
14265df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira    }
14275df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira
142879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private boolean shouldCreateChip(int start, int end) {
1429f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
143032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira    }
143132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira
143232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
1433f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (mNoChips) {
1434f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            return true;
1435f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1436194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips =
1437194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                getSpannable().getSpans(start, end, DrawableRecipientChip.class);
143832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        if ((chips == null || chips.length == 0)) {
143932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            return false;
144064af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira        }
144132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        return true;
144279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
144379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
144479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private void handleEdit(int start, int end) {
1445a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira        if (start == -1 || end == -1) {
1446a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            // This chip no longer exists in the field.
1447a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            dismissDropDown();
1448a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            return;
1449a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira        }
145079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        // This is in the middle of a chip, so select out the whole chip
145179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        // and commit it.
145279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        Editable editable = getText();
145379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        setSelection(end);
145479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        String text = getText().toString().substring(start, end);
1455fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira        if (!TextUtils.isEmpty(text)) {
1456f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            RecipientEntry entry = RecipientEntry.constructFakeEntry(text, isValid(text));
1457fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
1458fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira            CharSequence chipText = createChip(entry, false);
14595cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira            int selEnd = getSelectionEnd();
14605cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira            if (chipText != null && start > -1 && selEnd > -1) {
14615cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira                editable.replace(start, selEnd, chipText);
14622d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            }
1463fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira        }
1464ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira        dismissDropDown();
146564af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira    }
146664af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira
1467a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1468a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If there is a selected chip, delegate the key events
1469a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * to the selected chip.
1470a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1471cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1472cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
147395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
147495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
147595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                mAlternatesPopup.dismiss();
147695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
147795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            removeChip(mSelectedChip);
1478cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1479cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
14802fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy        switch (keyCode) {
14812fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy            case KeyEvent.KEYCODE_ENTER:
14822fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy            case KeyEvent.KEYCODE_DPAD_CENTER:
14832fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                if (event.hasNoModifiers()) {
14842fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    if (commitDefault()) {
14852fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        return true;
14862fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    }
14872fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    if (mSelectedChip != null) {
14882fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        clearSelectedChip();
14892fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        return true;
14902fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    } else if (focusNext()) {
14912fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        return true;
14922fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    }
14932fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                }
14942fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                break;
14952fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy        }
14962fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy
1497cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return super.onKeyDown(keyCode, event);
14989159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
14999159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
150001162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
150101162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    /* package */ Spannable getSpannable() {
1502e33555f13a9b05d835cb860e2c30ef40af3c8502Erik        return getText();
1503cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1504cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1505194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private int getChipStart(DrawableRecipientChip chip) {
150695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return getSpannable().getSpanStart(chip);
150795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
150895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1509194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private int getChipEnd(DrawableRecipientChip chip) {
151095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return getSpannable().getSpanEnd(chip);
151195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
151295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1513cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /**
1514cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1515cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * this subclass method filters on the range from
1516cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1517cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1518cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * and makes sure that the range is not already a Chip.
1519cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     */
1520cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1521cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
1522093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        boolean isCompletedToken = isCompletedToken(text);
1523093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (enoughToFilter() && !isCompletedToken) {
1524cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            int end = getSelectionEnd();
1525cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1526cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // If this is a RecipientChip, don't filter
1527cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // on its contents.
1528cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            Spannable span = getSpannable();
1529194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip[] chips = span.getSpans(start, end, DrawableRecipientChip.class);
1530cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (chips != null && chips.length > 0) {
153141e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin                dismissDropDown();
1532cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                return;
1533cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1534093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        } else if (isCompletedToken) {
153541e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin            dismissDropDown();
1536093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            return;
1537cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1538cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        super.performFiltering(text, keyCode);
1539cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1540cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
154120c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
154220c9d620e79ae28994856541761a951074551518Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
154320c9d620e79ae28994856541761a951074551518Mindy Pereira        if (TextUtils.isEmpty(text)) {
154420c9d620e79ae28994856541761a951074551518Mindy Pereira            return false;
154520c9d620e79ae28994856541761a951074551518Mindy Pereira        }
154620c9d620e79ae28994856541761a951074551518Mindy Pereira        // Check to see if this is a completed token before filtering.
154720c9d620e79ae28994856541761a951074551518Mindy Pereira        int end = text.length();
154820c9d620e79ae28994856541761a951074551518Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
154920c9d620e79ae28994856541761a951074551518Mindy Pereira        String token = text.toString().substring(start, end).trim();
155020c9d620e79ae28994856541761a951074551518Mindy Pereira        if (!TextUtils.isEmpty(token)) {
155120c9d620e79ae28994856541761a951074551518Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
155220c9d620e79ae28994856541761a951074551518Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
155320c9d620e79ae28994856541761a951074551518Mindy Pereira        }
155420c9d620e79ae28994856541761a951074551518Mindy Pereira        return false;
155520c9d620e79ae28994856541761a951074551518Mindy Pereira    }
155620c9d620e79ae28994856541761a951074551518Mindy Pereira
1557cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private void clearSelectedChip() {
1558cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        if (mSelectedChip != null) {
155995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            unselectChip(mSelectedChip);
1560cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            mSelectedChip = null;
1561cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1562b86dcd5230ebcc57e5fc7a669c2304aca142dbf5Mindy Pereira        setCursorVisible(true);
1563cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1564cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1565a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1566a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
1567a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the view does not have focus, any tap on the view
1568a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * will just focus the view. If the view has focus, determine
1569a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
1570a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * is not selected, select it and clear any other selected chips.
1571a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If it isn't, then select that chip.
1572a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1573cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1574cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
157505dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        if (!isFocused()) {
157605dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            // Ignore any chip taps until this view is focused.
157705dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            return super.onTouchEvent(event);
157805dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
1579cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        boolean handled = super.onTouchEvent(event);
158005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        int action = event.getAction();
1581cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        boolean chipWasSelected = false;
15822e905906f83fb1285498f09fce4db4a5878efbccMindy Pereira        if (mSelectedChip == null) {
15831d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira            mGestureDetector.onTouchEvent(event);
15841d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
1585bfedc1e199e57dcda494389fdca0750e1f165135Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1586cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            float x = event.getX();
1587cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            float y = event.getY();
15881650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy            int offset = putOffsetInRange(x, y);
1589194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip currentChip = findChip(offset);
1590cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (currentChip != null) {
1591cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1592cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1593cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                        clearSelectedChip();
159495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1595cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    } else if (mSelectedChip == null) {
1596a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira                        setSelection(getText().length());
1597a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira                        commitDefault();
159895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1599cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    } else {
160095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1601cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    }
1602cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                }
1603cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                chipWasSelected = true;
1604c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                handled = true;
16055e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            } else if (mSelectedChip != null && shouldShowEditableText(mSelectedChip)) {
1606c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira                chipWasSelected = true;
1607cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1608cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1609cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1610cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            clearSelectedChip();
1611cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1612cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return handled;
1613cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1614cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1615c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private void scrollLineIntoView(int line) {
1616c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        if (mScrollView != null) {
1617093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            mScrollView.smoothScrollBy(0, calculateOffsetFromBottom(line));
1618c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        }
1619c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    }
1620c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
1621272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy    private void showAlternates(final DrawableRecipientChip currentChip,
1622272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            final ListPopupWindow alternatesPopup, final int width) {
1623272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy        new AsyncTask<Void, Void, ListAdapter>() {
1624272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            @Override
1625272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            protected ListAdapter doInBackground(final Void... params) {
1626272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                return createAlternatesAdapter(currentChip);
1627272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            }
1628272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy
16296b7110f320c978c368c28bdb06212c6a6df12f1fAlice Yang            @Override
1630272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            protected void onPostExecute(final ListAdapter result) {
1631fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler                if (!mAttachedToWindow) {
1632fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler                    return;
1633fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler                }
1634272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                int line = getLayout().getLineForOffset(getChipStart(currentChip));
1635272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                int bottom;
1636272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                if (line == getLineCount() -1) {
1637272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    bottom = 0;
1638272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                } else {
1639272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    bottom = -(int) ((mChipHeight + (2 * mLineSpacingExtra)) * (Math
1640272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                            .abs(getLineCount() - 1 - line)));
1641272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                }
1642272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // Align the alternates popup with the left side of the View,
1643272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // regardless of the position of the chip tapped.
1644272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setWidth(width);
1645272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setAnchorView(RecipientEditTextView.this);
1646272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setVerticalOffset(bottom);
1647272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setAdapter(result);
1648272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setOnItemClickListener(mAlternatesListener);
1649272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // Clear the checked item.
1650272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                mCheckedItem = -1;
1651272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.show();
1652272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                ListView listView = alternatesPopup.getListView();
1653272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1654272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // Checked item would be -1 if the adapter has not
1655272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // loaded the view that should be checked yet. The
1656272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // variable will be set correctly when onCheckedItemChanged
1657272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // is called in a separate thread.
1658272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                if (mCheckedItem != -1) {
1659272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    listView.setItemChecked(mCheckedItem, true);
1660272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    mCheckedItem = -1;
1661272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                }
1662272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            }
1663272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy        }.execute((Void[]) null);
166495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
166595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1666194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
16677a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(),
16687a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy                chip.getDirectoryId(), chip.getLookupKey(), chip.getDataId(),
1669b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                getAdapter().getQueryType(), this, mDropdownChipLayouter);
167095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
167195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1672194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) {
1673b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(),
1674b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                mDropdownChipLayouter);
167501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
167601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
1677aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler    @Override
167895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    public void onCheckedItemChanged(int position) {
167995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
168095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
168195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            listView.setItemChecked(position, true);
168295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        }
168398b547f2eeb80259036e3f528636d7cbd823bf6dMindy Pereira        mCheckedItem = position;
168495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
168595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
16861650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int putOffsetInRange(final float x, final float y) {
16871650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int offset;
16881650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
16891650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
16901650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy            offset = getOffsetForPosition(x, y);
16911650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        } else {
16921650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy            offset = supportGetOffsetForPosition(x, y);
16931650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        }
16941650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
16951650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return putOffsetInRange(offset);
16961650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
16971650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
1698cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1699cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1700cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // what comes before the finger.
1701cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private int putOffsetInRange(int o) {
1702cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int offset = o;
1703cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Editable text = getText();
1704cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int length = text.length();
1705cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Remove whitespace from end to find "real end"
1706cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int realLength = length;
1707cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1708cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (text.charAt(i) == ' ') {
1709cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                realLength--;
1710cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            } else {
1711cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                break;
1712cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1713cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1714cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1715fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // If the offset is beyond or at the end of the text,
1716fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // leave it alone.
1717fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        if (offset >= realLength) {
1718cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            return offset;
1719cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1720fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        Editable editable = getText();
1721b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1722cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // Keep walking backward!
1723cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            offset--;
1724cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1725cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return offset;
1726cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1727cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1728f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private static int findText(Editable text, int offset) {
1729fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        if (text.charAt(offset) != ' ') {
1730fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira            return offset;
1731fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        }
1732fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        return -1;
1733fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    }
1734fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira
1735194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private DrawableRecipientChip findChip(int offset) {
1736194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips =
1737194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                getSpannable().getSpans(0, getText().length(), DrawableRecipientChip.class);
1738cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Find the chip that contains this offset.
1739cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        for (int i = 0; i < chips.length; i++) {
1740194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip = chips[i];
174195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int start = getChipStart(chip);
174295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int end = getChipEnd(chip);
174395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (offset >= start && offset <= end) {
1744cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                return chip;
1745cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1746cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1747cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return null;
1748cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1749cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
175001162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
1751aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Use this method to generate text to add to the list of addresses.
175232aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira    /* package */String createAddressText(RecipientEntry entry) {
17533ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        String display = entry.getDisplayName();
17543ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        String address = entry.getDestination();
17553ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
17563ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira            display = null;
17573ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        }
1758abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        String trimmedDisplayText;
1759c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor        if (isPhoneQuery() && isPhoneNumber(address)) {
1760abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            trimmedDisplayText = address.trim();
1761abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        } else {
1762abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            if (address != null) {
1763abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                // Tokenize out the address in case the address already
1764abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                // contained the username as well.
1765abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1766abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                if (tokenized != null && tokenized.length > 0) {
1767abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                    address = tokenized[0].getAddress();
1768abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                }
1769ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira            }
1770abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            Rfc822Token token = new Rfc822Token(display, address, null);
1771abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            trimmedDisplayText = token.toString().trim();
1772d8c15c328eaa109946f8f9093f3c2f2773d525ddMindy Pereira        }
17733ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        int index = trimmedDisplayText.indexOf(",");
177432aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira        return mTokenizer != null && !TextUtils.isEmpty(trimmedDisplayText)
177532aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira                && index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
1776aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
1777aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    }
1778aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
1779aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
1780aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Use this method to generate text to display in a chip.
1781aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
1782aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String display = entry.getDisplayName();
1783aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String address = entry.getDestination();
1784aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
1785aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            display = null;
1786aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
1787aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (!TextUtils.isEmpty(display)) {
1788aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return display;
1789aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
1790aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return address;
1791aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        } else {
1792aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return new Rfc822Token(display, address, null).toString();
1793aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
17943ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira    }
17953ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira
17960ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1797aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String displayText = createAddressText(entry);
17982d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
17992d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            return null;
18002d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        }
1801f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        SpannableString chipText = null;
1802cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Always leave a blank space at the end of a chip.
1803f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy        int textLength = displayText.length() - 1;
1804f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        chipText = new SpannableString(displayText);
1805f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (!mNoChips) {
1806f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            try {
18071db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein                DrawableRecipientChip chip = constructChipSpan(entry, pressed);
1808f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                chipText.setSpan(chip, 0, textLength,
1809f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1810f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                chip.setOriginalText(chipText.toString());
1811f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            } catch (NullPointerException e) {
1812f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1813f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                return null;
1814f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            }
1815cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1816cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return chipText;
1817cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1818cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1819a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1820a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
1821a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * contact information of the selected item.
1822a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1823cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1824cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1825114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor        if (position < 0) {
1826114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor            return;
1827114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor        }
18280f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein
18290f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein        final int charactersTyped = submitItemAtPosition(position);
18300f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein        if (charactersTyped > -1 && mRecipientEntryItemClickedListener != null) {
18310f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein            mRecipientEntryItemClickedListener
18320f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein                    .onRecipientEntryItemClicked(charactersTyped, position);
18330f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein        }
1834cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1835cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
18360f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein    private int submitItemAtPosition(int position) {
1837858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy        RecipientEntry entry = createValidatedEntry(getAdapter().getItem(position));
1838f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik        if (entry == null) {
18390f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein            return -1;
1840f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik        }
1841cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        clearComposingText();
1842cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1843cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int end = getSelectionEnd();
1844cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1845cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1846cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Editable editable = getText();
1847cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
1848e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira        CharSequence chip = createChip(entry, false);
1849005b2e29742d7b9f3ecc505a50b11d91bd44c818Mindy Pereira        if (chip != null && start >= 0 && end >= 0) {
1850e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira            editable.replace(start, end, chip);
1851e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira        }
18525df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        sanitizeBetween();
18530f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein
18540f2b174c6d8a19fb8ffbd71f742e11d3a04add9bAndrew Sapperstein        return end - start;
1855cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1856cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
18573bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
18583bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        if (item == null) {
18593bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            return null;
18603bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
18613bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        final RecipientEntry entry;
18623bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // If the display name and the address are the same, or if this is a
18633bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
18643bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // recipient that is editable.
18653bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        String destination = item.getDestination();
18665e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (!isPhoneQuery() && item.getContactId() == RecipientEntry.GENERATED_CONTACT) {
18675e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            entry = RecipientEntry.constructGeneratedEntry(item.getDisplayName(),
1868f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    destination, item.isValid());
18695e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        } else if (RecipientEntry.isCreatedRecipient(item.getContactId())
1870b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                && (TextUtils.isEmpty(item.getDisplayName())
1871b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                        || TextUtils.equals(item.getDisplayName(), destination)
1872b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                        || (mValidator != null && !mValidator.isValid(destination)))) {
1873f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            entry = RecipientEntry.constructFakeEntry(destination, item.isValid());
18743bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        } else {
18753bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            entry = item;
18763bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
18773bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        return entry;
18783bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
18793bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
188001162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
1881194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /* package */DrawableRecipientChip[] getSortedRecipients() {
1882194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] recips = getSpannable()
1883194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                .getSpans(0, getText().length(), DrawableRecipientChip.class);
1884194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        ArrayList<DrawableRecipientChip> recipientsList = new ArrayList<DrawableRecipientChip>(
1885194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                Arrays.asList(recips));
1886e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        final Spannable spannable = getSpannable();
1887194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        Collections.sort(recipientsList, new Comparator<DrawableRecipientChip>() {
1888e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
1889e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            @Override
1890194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            public int compare(DrawableRecipientChip first, DrawableRecipientChip second) {
1891e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int firstStart = spannable.getSpanStart(first);
1892e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int secondStart = spannable.getSpanStart(second);
1893e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                if (firstStart < secondStart) {
1894e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return -1;
1895e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                } else if (firstStart > secondStart) {
1896e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return 1;
1897e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                } else {
1898e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return 0;
1899e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                }
1900e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            }
1901e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        });
1902194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        return recipientsList.toArray(new DrawableRecipientChip[recipientsList.size()]);
1903e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    }
1904e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
190512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
190612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
190712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
190812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
190912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
191012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
191112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
191212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
191312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
191412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
191512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
191612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
191712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
191812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1919a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1920a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * No chips are selectable.
1921a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
192212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
192312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
192412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
192512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
192612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1927d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
1928d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ImageSpan getMoreChip() {
1929dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
1930dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                MoreImageSpan.class);
1931dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
1932dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    }
1933dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira
1934f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private MoreImageSpan createMoreSpan(int count) {
1935f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), count);
1936f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1937f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1938f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1939f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1940f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                + mMoreItem.getPaddingRight();
1941f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int height = getLineHeight();
1942f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1943f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Canvas canvas = new Canvas(drawable);
1944f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int adjustedHeight = height;
1945f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Layout layout = getLayout();
1946f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (layout != null) {
1947f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            adjustedHeight -= layout.getLineDescent(0);
1948f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1949f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
1950f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1951f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
1952f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        result.setBounds(0, 0, width, height);
1953f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return new MoreImageSpan(result);
1954f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1955f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1956f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1957f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ void createMoreChipPlainText() {
1958f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        // Take the first <= CHIP_LIMIT addresses and get to the end of the second one.
1959f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Editable text = getText();
1960f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int start = 0;
1961f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int end = start;
1962f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        for (int i = 0; i < CHIP_LIMIT; i++) {
1963f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            end = movePastTerminators(mTokenizer.findTokenEnd(text, start));
1964f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            start = end; // move to the next token and get its end.
1965f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1966f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        // Now, count total addresses.
1967f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        start = 0;
1968f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int tokenCount = countTokens(text);
1969f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT);
1970f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(end, text.length()));
1971f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1972f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        text.replace(end, text.length(), chipText);
1973f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        mMoreChip = moreSpan;
1974f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1975f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1976f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1977f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /* package */int countTokens(Editable text) {
1978f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int tokenCount = 0;
1979f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int start = 0;
1980f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        while (start < text.length()) {
1981f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            start = movePastTerminators(mTokenizer.findTokenEnd(text, start));
1982f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            tokenCount++;
1983f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            if (start >= text.length()) {
1984f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                break;
1985f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            }
1986f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1987f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return tokenCount;
1988f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1989f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1990a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1991342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1992342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * do not fit in the pre-defined available space when the
1993342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * RecipientEditTextView loses focus.
1994a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1995d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
1996d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ void createMoreChip() {
1997f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (mNoChips) {
1998f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            createMoreChipPlainText();
1999f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            return;
2000f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
2001f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
2002076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        if (!mShouldShrink) {
2003076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira            return;
2004076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        }
2005e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
2006e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        if (tempMore.length > 0) {
2007e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
2008e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        }
2009194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] recipients = getSortedRecipients();
2010f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
2011d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
20123bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            mMoreChip = null;
20133bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            return;
201412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
2015e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        Spannable spannable = getSpannable();
2016d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira        int numRecipients = recipients.length;
201712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
2018f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(overage);
2019194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        mRemovedSpans = new ArrayList<DrawableRecipientChip>();
202012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int totalReplaceStart = 0;
202112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int totalReplaceEnd = 0;
2022e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        Editable text = getText();
202321cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
202421cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            mRemovedSpans.add(recipients[i]);
2025364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            if (i == numRecipients - overage) {
202621cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
2027364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            }
202821cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            if (i == recipients.length - 1) {
202921cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
2030364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            }
2031e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
2032e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
2033e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
2034e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
20351852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
203621cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            spannable.removeSpan(recipients[i]);
203712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
2038f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (totalReplaceEnd < text.length()) {
2039f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            totalReplaceEnd = text.length();
2040f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
20411852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
20421852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
20431852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
204412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
20451852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        text.replace(start, end, chipText);
20463bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        mMoreChip = moreSpan;
20475e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        // If adding the +more chip goes over the limit, resize accordingly.
20485e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (!isPhoneQuery() && getLineCount() > mMaxLines) {
20495e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            setMaxLines(getLineCount());
20505e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        }
205112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
205212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
2053a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
2054a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
2055a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
2056a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
2057d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
2058d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /*package*/ void removeMoreChip() {
205912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        if (mMoreChip != null) {
206012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            Spannable span = getSpannable();
206112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            span.removeSpan(mMoreChip);
206212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            mMoreChip = null;
206312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            // Re-add the spans that were removed.
206412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
206512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                // Recreate each removed span.
2066194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] recipients = getSortedRecipients();
206754effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                // Start the search for tokens after the last currently visible
206854effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                // chip.
2069ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                if (recipients == null || recipients.length == 0) {
2070ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                    return;
2071ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                }
207254effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
207312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                Editable editable = getText();
2074194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                for (DrawableRecipientChip chip : mRemovedSpans) {
2075e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    int chipStart;
20763bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    int chipEnd;
20773bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    String token;
2078e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    // Need to find the location of the chip, again.
2079e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    token = (String) chip.getOriginalText();
208054effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // As we find the matching recipient for the remove spans,
208154effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // reduce the size of the string we need to search.
208254effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // That way, if there are duplicates, we always find the correct
208354effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // recipient.
208454effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    chipStart = editable.toString().indexOf(token, end);
208554effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
20860d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    // Only set the span if we found a matching token.
20870d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    if (chipStart != -1) {
20880d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
20890d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
20900d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    }
209112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                }
209212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                mRemovedSpans.clear();
209312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            }
209412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
209512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
209612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
2097cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /**
209895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
209995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
210095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
210195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
210295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * will change the background color of the chip, show the delete icon,
210395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * and a popup window with the address in use highlighted and any other
210495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * alternate addresses for the contact.
210595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @param currentChip Chip to select.
210695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
210795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * just contained an email address.
2108cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     */
2109194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private DrawableRecipientChip selectChip(DrawableRecipientChip currentChip) {
21105e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (shouldShowEditableText(currentChip)) {
211101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            CharSequence text = currentChip.getValue();
211201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            Editable editable = getText();
2113f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            Spannable spannable = getSpannable();
2114f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            int spanStart = spannable.getSpanStart(currentChip);
2115f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            int spanEnd = spannable.getSpanEnd(currentChip);
2116f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            spannable.removeSpan(currentChip);
2117f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            editable.delete(spanStart, spanEnd);
211801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            setCursorVisible(true);
211901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            setSelection(editable.length());
2120f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            editable.append(text);
2121f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            return constructChipSpan(
2122f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    RecipientEntry.constructFakeEntry((String) text, isValid(text.toString())),
21231db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein                    true);
21248af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein        } else {
212595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int start = getChipStart(currentChip);
212695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int end = getChipEnd(currentChip);
212795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            getSpannable().removeSpan(currentChip);
2128194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip newChip;
21298af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            final boolean showAddress =
21308af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT ||
21318af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                    getAdapter().forceShowAddress();
213295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            try {
21338af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                if (showAddress && mNoChips) {
2134f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    return null;
2135f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
21361db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein                newChip = constructChipSpan(currentChip.getEntry(), true);
213795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            } catch (NullPointerException e) {
213895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
213995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                return null;
214095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
21410ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira            Editable editable = getText();
214295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
2143d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            if (start == -1 || end == -1) {
214495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
2145d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            } else {
214604da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
2147d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            }
214895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            newChip.setSelected(true);
21495e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            if (shouldShowEditableText(newChip)) {
215001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
215181fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira            }
21528af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            if (showAddress) {
21538af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                showAddress(newChip, mAddressPopup, getWidth());
215401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            } else {
21558af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein                showAlternates(newChip, mAlternatesPopup, getWidth());
215601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
21574e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            setCursorVisible(false);
215801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            return newChip;
21590ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira        }
216095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
21610ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira
2162194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private boolean shouldShowEditableText(DrawableRecipientChip currentChip) {
21635e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        long contactId = currentChip.getContactId();
21645e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        return contactId == RecipientEntry.INVALID_CONTACT
21655e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT);
21665e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp    }
2167cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
2168194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private void showAddress(final DrawableRecipientChip currentChip, final ListPopupWindow popup,
2169f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy            int width) {
2170fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        if (!mAttachedToWindow) {
2171fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler            return;
2172fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        }
217301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
217401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
217501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        // Align the alternates popup with the left side of the View,
217601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        // regardless of the position of the chip tapped.
217701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setWidth(width);
217801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setAnchorView(this);
217901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setVerticalOffset(bottom);
218001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
218101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
218201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            @Override
218301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
218401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                unselectChip(currentChip);
218501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                popup.dismiss();
218601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
218701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        });
218801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.show();
218901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        ListView listView = popup.getListView();
219001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
219101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        listView.setItemChecked(0, true);
219201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
219301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
219495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
219595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
2196f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira     * the chip without a delete icon and with an unfocused background. This is
2197f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira     * called when the RecipientChip no longer has focus.
219895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2199194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private void unselectChip(DrawableRecipientChip chip) {
220095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int start = getChipStart(chip);
220195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int end = getChipEnd(chip);
220295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable editable = getText();
2203e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira        mSelectedChip = null;
220495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (start == -1 || end == -1) {
2205f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
2206c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            setSelection(editable.length());
2207c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            commitDefault();
220895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        } else {
2209e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira            getSpannable().removeSpan(chip);
221095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
221104da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            editable.removeSpan(chip);
221204da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            try {
2213f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (!mNoChips) {
22141db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein                    editable.setSpan(constructChipSpan(chip.getEntry(), false),
22155e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                            start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
2216f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
221704da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            } catch (NullPointerException e) {
221804da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira                Log.e(TAG, e.getMessage(), e);
221904da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            }
2220cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
222195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setCursorVisible(true);
222295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setSelection(editable.length());
222395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
222495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mAlternatesPopup.dismiss();
2225cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
222695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2227cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
222895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
222995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Return whether a touch event was inside the delete target of
223095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * a selected chip. It is in the delete target if:
223195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * 1) the x and y points of the event are within the
223295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * delete assset.
223395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
223495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * right after the selected chip.
223595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @return boolean
223695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2237194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private boolean isInDelete(DrawableRecipientChip chip, int offset, float x, float y) {
223895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Figure out the bounds of this chip and whether or not
223995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // the user clicked in the X portion.
2240f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy        // TODO: Should x and y be used, or removed?
2241b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (mDisableDelete) {
2242b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            return false;
2243b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        }
2244b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
2245b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return chip.isSelected() &&
2246c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                ((mAvatarPosition == AVATAR_POSITION_END && offset == getChipEnd(chip)) ||
2247c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                (mAvatarPosition != AVATAR_POSITION_END && offset == getChipStart(chip)));
224895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
22495086391a478c3b1badbb86074c3cef72126c7d0fMindy Pereira
225095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
225195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
225295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
225397cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
2254b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /* package */void removeChip(DrawableRecipientChip chip) {
225595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Spannable spannable = getSpannable();
225695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
225795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
225895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable text = getText();
225962397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        int toDelete = spanEnd;
226095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
226195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Clear that there is a selected chip before updating any text.
226295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
226395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mSelectedChip = null;
2264cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
226562397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        // Always remove trailing spaces when removing a chip.
226662397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
226762397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira            toDelete++;
226862397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        }
226995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        spannable.removeSpan(chip);
2270f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira        if (spanStart >= 0 && toDelete > 0) {
2271f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira            text.delete(spanStart, toDelete);
2272f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira        }
227395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
227495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            clearSelectedChip();
2275cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
227695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2277cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
227895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
227995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Replace this currently selected chip with a new chip
228095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * that uses the contact data provided.
228195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2282aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
2283194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /*package*/ void replaceChip(DrawableRecipientChip chip, RecipientEntry entry) {
228495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
228595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
228695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mSelectedChip = null;
2287cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
228895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int start = getChipStart(chip);
228995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int end = getChipEnd(chip);
229095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        getSpannable().removeSpan(chip);
229195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable editable = getText();
229295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        CharSequence chipText = createChip(entry, false);
22932d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        if (chipText != null) {
22942d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            if (start == -1 || end == -1) {
22952d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
22962d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                editable.insert(0, chipText);
22972d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            } else {
22982d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
22992d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    // There may be a space to replace with this chip's new
2300b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp                    // associated space. Check for it
23012d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    int toReplace = end;
23022d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
23032d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
23042d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                        toReplace++;
23052d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    }
23062d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    editable.replace(start, toReplace, chipText);
230762397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira                }
23087ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira            }
2309cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
231095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setCursorVisible(true);
231195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
231295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            clearSelectedChip();
2313cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
231495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2315cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
231695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
231795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
231895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
231995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Otherwise, unselect the chip.
232095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2321194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    public void onClick(DrawableRecipientChip chip, int offset, float x, float y) {
232295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (chip.isSelected()) {
232395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
232495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                removeChip(chip);
232595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            } else {
232695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                clearSelectedChip();
2327cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
2328cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
232995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2330cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
233121cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira    private boolean chipsPending() {
233221cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
233321cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira    }
233421cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira
2335883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    @Override
2336883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
2337883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira        mTextWatcher = null;
2338883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira        super.removeTextChangedListener(watcher);
2339883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    }
2340883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira
2341b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao    private boolean isValidEmailAddress(String input) {
2342b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao        return !TextUtils.isEmpty(input) && mValidator != null &&
2343b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao                mValidator.isValid(input);
2344b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao    }
2345b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao
234679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private class RecipientTextWatcher implements TextWatcher {
234772a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira
234879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
234979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void afterTextChanged(Editable s) {
235032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // If the text has been set to null or empty, make sure we remove
235132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // all the spans we applied.
235232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            if (TextUtils.isEmpty(s)) {
235332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                // Remove all the chips spans.
235432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                Spannable spannable = getSpannable();
2355194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] chips = spannable.getSpans(0, getText().length(),
2356194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        DrawableRecipientChip.class);
2357194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                for (DrawableRecipientChip chip : chips) {
235832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                    spannable.removeSpan(chip);
235932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                }
236032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                if (mMoreChip != null) {
236132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                    spannable.removeSpan(mMoreChip);
236232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                }
236341e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin                clearSelectedChip();
236432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                return;
236532366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            }
236601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            // Get whether there are any recipients pending addition to the
236701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            // view. If there are, don't do anything in the text watcher.
236821cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            if (chipsPending()) {
236979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                return;
237079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
2371c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            // If the user is editing a chip, don't clear it.
2372f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            if (mSelectedChip != null) {
2373f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                if (!isGeneratedContact(mSelectedChip)) {
2374f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    setCursorVisible(true);
2375f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    setSelection(getText().length());
2376f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    clearSelectedChip();
2377f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                } else {
2378f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    return;
2379f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                }
238079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
238179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int length = s.length();
238279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // Make sure there is content there to parse and that it is
2383ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira            // not just the commit character.
238479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (length > 1) {
2385f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                if (lastCharacterIsCommitCharacter(s)) {
2386f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    commitByCharacter();
2387f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    return;
2388f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                }
2389ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                char last;
239011a2adb53f764ffcadd262678ff782e6b4992decMindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
2391ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                int len = length() - 1;
2392ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                if (end != len) {
2393ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                    last = s.charAt(end);
2394ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                } else {
2395ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                    last = s.charAt(len);
2396ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                }
2397f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                if (last == COMMIT_CHAR_SPACE) {
23984afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                    if (!isPhoneQuery()) {
23994afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        // Check if this is a valid email address. If it is,
24004afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        // commit it.
24014afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        String text = getText().toString();
24024afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
24034afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
24044afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                                tokenStart));
2405b3b248acd845b017e952fb802e0976e82fc1c583Jin Cao                        if (isValidEmailAddress(sub)) {
24064afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                            commitByCharacter();
24074afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        }
240879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    }
240979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                }
241079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
241179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
241279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
241379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
241479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
2415f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            // The user deleted some text OR some text was replaced; check to
2416f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            // see if the insertion point is on a space
241772a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            // following a chip.
2418f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            if (before - count == 1) {
241972a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                // If the item deleted is a space, and the thing before the
242072a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                // space is a chip, delete the entire span.
242172a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                int selStart = getSelectionStart();
2422194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
2423194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        DrawableRecipientChip.class);
242472a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                if (repl.length > 0) {
242572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    // There is a chip there! Just remove it.
242672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    Editable editable = getText();
242772a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    // Add the separator token.
242872a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    int tokenStart = mTokenizer.findTokenStart(editable, selStart);
242972a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    int tokenEnd = mTokenizer.findTokenEnd(editable, tokenStart);
243062fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    tokenEnd = tokenEnd + 1;
243162fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    if (tokenEnd > editable.length()) {
243262fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                        tokenEnd = editable.length();
243362fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    }
243462fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    editable.delete(tokenStart, tokenEnd);
243572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    getSpannable().removeSpan(repl[0]);
243672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                }
24374e7d20f7b74ddce5a104138714c9683c8bd2300dMindy Pereira            } else if (count > before) {
24387e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                if (mSelectedChip != null
24397e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                    && isGeneratedContact(mSelectedChip)) {
24407e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                    if (lastCharacterIsCommitCharacter(s)) {
24417e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                        commitByCharacter();
24427e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                        return;
24437e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                    }
24447e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                }
244572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            }
244679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
244779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
244879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
244979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2450dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            // Do nothing.
245179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
245279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
24531852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
2454c1b5258fe0d244be2d4d8c4d2969c1b8859c6fa8Alice Yang   public boolean lastCharacterIsCommitCharacter(CharSequence s) {
2455f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        char last;
2456f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
2457f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        int len = length() - 1;
2458f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        if (end != len) {
2459f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            last = s.charAt(end);
2460f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        } else {
2461f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            last = s.charAt(len);
2462f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        }
2463f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return last == COMMIT_CHAR_COMMA || last == COMMIT_CHAR_SEMICOLON;
2464f30a42800318f6790d55421f8f6980eb38db4d3cmindyp    }
2465f30a42800318f6790d55421f8f6980eb38db4d3cmindyp
2466194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    public boolean isGeneratedContact(DrawableRecipientChip chip) {
2467f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        long contactId = chip.getContactId();
2468f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return contactId == RecipientEntry.INVALID_CONTACT
2469f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT);
2470f30a42800318f6790d55421f8f6980eb38db4d3cmindyp    }
2471f30a42800318f6790d55421f8f6980eb38db4d3cmindyp
2472e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2473e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
2474e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2475e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private void handlePasteClip(ClipData clip) {
2476e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        removeTextChangedListener(mTextWatcher);
2477e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2478e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
2479e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            for (int i = 0; i < clip.getItemCount(); i++) {
2480e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                CharSequence paste = clip.getItemAt(i).getText();
2481e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                if (paste != null) {
2482e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    int start = getSelectionStart();
2483e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    int end = getSelectionEnd();
2484e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    Editable editable = getText();
2485e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    if (start >= 0 && end >= 0 && start != end) {
2486e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                        editable.append(paste, start, end);
2487e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    } else {
2488e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                        editable.insert(end, paste);
2489e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    }
2490e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    handlePasteAndReplace();
2491e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                }
2492e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            }
2493e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2494e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2495e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        mHandler.post(mAddTextWatcher);
2496e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2497e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
249820c9d620e79ae28994856541761a951074551518Mindy Pereira    @Override
249920c9d620e79ae28994856541761a951074551518Mindy Pereira    public boolean onTextContextMenuItem(int id) {
250020c9d620e79ae28994856541761a951074551518Mindy Pereira        if (id == android.R.id.paste) {
250120c9d620e79ae28994856541761a951074551518Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
250220c9d620e79ae28994856541761a951074551518Mindy Pereira                    Context.CLIPBOARD_SERVICE);
2503e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            handlePasteClip(clipboard.getPrimaryClip());
250420c9d620e79ae28994856541761a951074551518Mindy Pereira            return true;
250520c9d620e79ae28994856541761a951074551518Mindy Pereira        }
250620c9d620e79ae28994856541761a951074551518Mindy Pereira        return super.onTextContextMenuItem(id);
250720c9d620e79ae28994856541761a951074551518Mindy Pereira    }
250820c9d620e79ae28994856541761a951074551518Mindy Pereira
2509b4e244af14950aee7d612612d5406981315d3454Mindy Pereira    private void handlePasteAndReplace() {
2510194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        ArrayList<DrawableRecipientChip> created = handlePaste();
2511b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        if (created != null && created.size() > 0) {
2512b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            // Perform reverse lookups on the pasted contacts.
2513b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            IndividualReplacementTask replace = new IndividualReplacementTask();
2514b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            replace.execute(created);
2515b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        }
2516b4e244af14950aee7d612612d5406981315d3454Mindy Pereira    }
2517b4e244af14950aee7d612612d5406981315d3454Mindy Pereira
251820c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
2519194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /* package */ArrayList<DrawableRecipientChip> handlePaste() {
252020c9d620e79ae28994856541761a951074551518Mindy Pereira        String text = getText().toString();
252120c9d620e79ae28994856541761a951074551518Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
252220c9d620e79ae28994856541761a951074551518Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
252320c9d620e79ae28994856541761a951074551518Mindy Pereira        int tokenStart = originalTokenStart;
25240cae85010120a2a8490baf3488ed0bd2293e1d4djli        int prevTokenStart = 0;
2525194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip findChip = null;
2526194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        ArrayList<DrawableRecipientChip> created = new ArrayList<DrawableRecipientChip>();
252720c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenStart != 0) {
252820c9d620e79ae28994856541761a951074551518Mindy Pereira            // There are things before this!
25290cae85010120a2a8490baf3488ed0bd2293e1d4djli            while (tokenStart != 0 && findChip == null && tokenStart != prevTokenStart) {
253020c9d620e79ae28994856541761a951074551518Mindy Pereira                prevTokenStart = tokenStart;
253120c9d620e79ae28994856541761a951074551518Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
253220c9d620e79ae28994856541761a951074551518Mindy Pereira                findChip = findChip(tokenStart);
2533fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor                if (tokenStart == originalTokenStart && findChip == null) {
2534fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor                    break;
2535fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor                }
253620c9d620e79ae28994856541761a951074551518Mindy Pereira            }
253720c9d620e79ae28994856541761a951074551518Mindy Pereira            if (tokenStart != originalTokenStart) {
253820c9d620e79ae28994856541761a951074551518Mindy Pereira                if (findChip != null) {
253920c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenStart = prevTokenStart;
254020c9d620e79ae28994856541761a951074551518Mindy Pereira                }
254120c9d620e79ae28994856541761a951074551518Mindy Pereira                int tokenEnd;
2542194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip createdChip;
254320c9d620e79ae28994856541761a951074551518Mindy Pereira                while (tokenStart < originalTokenStart) {
2544b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(getText().toString(),
2545b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp                            tokenStart));
254620c9d620e79ae28994856541761a951074551518Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
254720c9d620e79ae28994856541761a951074551518Mindy Pereira                    createdChip = findChip(tokenStart);
2548c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira                    if (createdChip == null) {
2549c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira                        break;
2550c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira                    }
255120c9d620e79ae28994856541761a951074551518Mindy Pereira                    // +1 for the space at the end.
255220c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
2553b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                    created.add(createdChip);
255420c9d620e79ae28994856541761a951074551518Mindy Pereira                }
255520c9d620e79ae28994856541761a951074551518Mindy Pereira            }
255620c9d620e79ae28994856541761a951074551518Mindy Pereira        }
255720c9d620e79ae28994856541761a951074551518Mindy Pereira        // Take a look at the last token. If the token has been completed with a
255820c9d620e79ae28994856541761a951074551518Mindy Pereira        // commit character, create a chip.
255920c9d620e79ae28994856541761a951074551518Mindy Pereira        if (isCompletedToken(lastAddress)) {
256020c9d620e79ae28994856541761a951074551518Mindy Pereira            Editable editable = getText();
2561b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
2562b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            commitChip(tokenStart, editable.length(), editable);
2563b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            created.add(findChip(tokenStart));
256420c9d620e79ae28994856541761a951074551518Mindy Pereira        }
2565b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        return created;
256620c9d620e79ae28994856541761a951074551518Mindy Pereira    }
256720c9d620e79ae28994856541761a951074551518Mindy Pereira
256820c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
256920c9d620e79ae28994856541761a951074551518Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
257020c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenEnd >= length()) {
257120c9d620e79ae28994856541761a951074551518Mindy Pereira            return tokenEnd;
257220c9d620e79ae28994856541761a951074551518Mindy Pereira        }
257320c9d620e79ae28994856541761a951074551518Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
257420c9d620e79ae28994856541761a951074551518Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
257520c9d620e79ae28994856541761a951074551518Mindy Pereira            tokenEnd++;
257620c9d620e79ae28994856541761a951074551518Mindy Pereira        }
257720c9d620e79ae28994856541761a951074551518Mindy Pereira        // This token had not only an end token character, but also a space
257820c9d620e79ae28994856541761a951074551518Mindy Pereira        // separating it from the next token.
257920c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
258020c9d620e79ae28994856541761a951074551518Mindy Pereira            tokenEnd++;
258120c9d620e79ae28994856541761a951074551518Mindy Pereira        }
258220c9d620e79ae28994856541761a951074551518Mindy Pereira        return tokenEnd;
258320c9d620e79ae28994856541761a951074551518Mindy Pereira    }
258420c9d620e79ae28994856541761a951074551518Mindy Pereira
25851852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
2586194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        private DrawableRecipientChip createFreeChip(RecipientEntry entry) {
25871852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            try {
2588f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (mNoChips) {
2589f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    return null;
2590f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
25911db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein                return constructChipSpan(entry, false);
25921852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            } catch (NullPointerException e) {
25931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                Log.e(TAG, e.getMessage(), e);
25941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                return null;
25951852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
25961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
25971852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
25981852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        @Override
259978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        protected void onPreExecute() {
260078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            // Ensure everything is in chip-form already, so we don't have text that slowly gets
260178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            // replaced
2602194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final List<DrawableRecipientChip> originalRecipients =
2603194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    new ArrayList<DrawableRecipientChip>();
2604194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final DrawableRecipientChip[] existingChips = getSortedRecipients();
260578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            for (int i = 0; i < existingChips.length; i++) {
260678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                originalRecipients.add(existingChips[i]);
260778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
260878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            if (mRemovedSpans != null) {
260978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                originalRecipients.addAll(mRemovedSpans);
261078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
261178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
2612194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final List<DrawableRecipientChip> replacements =
2613194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    new ArrayList<DrawableRecipientChip>(originalRecipients.size());
261478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
2615194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            for (final DrawableRecipientChip chip : originalRecipients) {
261678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                if (RecipientEntry.isCreatedRecipient(chip.getEntry().getContactId())
261778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                        && getSpannable().getSpanStart(chip) != -1) {
261878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    replacements.add(createFreeChip(chip.getEntry()));
261978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                } else {
262078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    replacements.add(null);
262178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                }
262278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
262378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
262478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            processReplacements(originalRecipients, replacements);
262578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        }
262678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
262778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        @Override
26281852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        protected Void doInBackground(Void... params) {
26291852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            if (mIndividualReplacements != null) {
26301852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                mIndividualReplacements.cancel(true);
26311852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
26321852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // For each chip in the list, look up the matching contact.
26331852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // If there is a match, replace that chip with the matching
26341852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // chip.
2635194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final ArrayList<DrawableRecipientChip> recipients =
2636194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    new ArrayList<DrawableRecipientChip>();
2637194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip[] existingChips = getSortedRecipients();
26381852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
263978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                recipients.add(existingChips[i]);
26401852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
26411852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            if (mRemovedSpans != null) {
264278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                recipients.addAll(mRemovedSpans);
26431852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
264403cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira            ArrayList<String> addresses = new ArrayList<String>();
2645194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip;
264678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            for (int i = 0; i < recipients.size(); i++) {
264778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                chip = recipients.get(i);
2648f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                if (chip != null) {
264903cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira                    addresses.add(createAddressText(chip.getEntry()));
2650f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                }
26511852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
26527a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy            final BaseRecipientAdapter adapter = getAdapter();
26538af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            adapter.getMatchingRecipients(addresses, new RecipientMatchCallback() {
265416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                        @Override
265594fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        public void matchesFound(Map<String, RecipientEntry> entries) {
2656194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            final ArrayList<DrawableRecipientChip> replacements =
2657194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                                    new ArrayList<DrawableRecipientChip>();
2658194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            for (final DrawableRecipientChip temp : recipients) {
265916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                RecipientEntry entry = null;
2660f0579ee4ab41c021f20f78b25ecf4c526b308ec6Alice Yang                                if (temp != null && RecipientEntry.isCreatedRecipient(
2661f0579ee4ab41c021f20f78b25ecf4c526b308ec6Alice Yang                                        temp.getEntry().getContactId())
266216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        && getSpannable().getSpanStart(temp) != -1) {
266316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    // Replace this.
266416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    entry = createValidatedEntry(
266516923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            entries.get(tokenizeAddress(temp.getEntry()
266616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                                    .getDestination())));
266716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                }
266816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                if (entry != null) {
266916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    replacements.add(createFreeChip(entry));
267016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                } else {
267116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    replacements.add(null);
267216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                }
267316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                            }
267478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            processReplacements(recipients, replacements);
26751852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        }
267694fa301de71cc110671802eba8376c275b4055a4Scott Kennedy
267794fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        @Override
2678f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                        public void matchesNotFound(final Set<String> unfoundAddresses) {
2679194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            final List<DrawableRecipientChip> replacements =
2680194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                                    new ArrayList<DrawableRecipientChip>(unfoundAddresses.size());
268194fa301de71cc110671802eba8376c275b4055a4Scott Kennedy
2682194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            for (final DrawableRecipientChip temp : recipients) {
2683ad8781e6062f086aa4160eab2799b6848911aa3dAlice Yang                                if (temp != null && RecipientEntry.isCreatedRecipient(
2684ad8781e6062f086aa4160eab2799b6848911aa3dAlice Yang                                        temp.getEntry().getContactId())
268594fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                        && getSpannable().getSpanStart(temp) != -1) {
2686f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                                    if (unfoundAddresses.contains(
2687f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                                            temp.getEntry().getDestination())) {
268894fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                        replacements.add(createFreeChip(temp.getEntry()));
268994fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                    } else {
269094fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                        replacements.add(null);
269194fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                    }
269294fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                } else {
269394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                    replacements.add(null);
269494fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                }
269594fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                            }
269694fa301de71cc110671802eba8376c275b4055a4Scott Kennedy
269778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            processReplacements(recipients, replacements);
269894fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        }
269916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                    });
27001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            return null;
27011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
270278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
2703194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        private void processReplacements(final List<DrawableRecipientChip> recipients,
2704194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                final List<DrawableRecipientChip> replacements) {
270578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            if (replacements != null && replacements.size() > 0) {
270678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                final Runnable runnable = new Runnable() {
270778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    @Override
270878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    public void run() {
27090e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                        final Editable text = new SpannableStringBuilder(getText());
271078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                        int i = 0;
27110e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                        for (final DrawableRecipientChip chip : recipients) {
27120e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                            final DrawableRecipientChip replacement = replacements.get(i);
271378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            if (replacement != null) {
271478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                final RecipientEntry oldEntry = chip.getEntry();
271578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                final RecipientEntry newEntry = replacement.getEntry();
271678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                final boolean isBetter =
271778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        RecipientAlternatesAdapter.getBetterRecipient(
271878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                oldEntry, newEntry) == newEntry;
271978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
272078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                if (isBetter) {
272178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                    // Find the location of the chip in the text currently shown.
27220e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                    final int start = text.getSpanStart(chip);
272378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                    if (start != -1) {
272478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // Replacing the entirety of what the chip represented,
272578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // including the extra space dividing it from other chips.
2726b023f69b1f35199bc17f1d2708acb492d3ae49b0Paul Westbrook                                        final int end =
2727b023f69b1f35199bc17f1d2708acb492d3ae49b0Paul Westbrook                                                Math.min(text.getSpanEnd(chip) + 1, text.length());
27280e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                        text.removeSpan(chip);
272978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // Make sure we always have just 1 space at the end to
273078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // separate this chip from the next chip.
27310e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                        final SpannableString displayText =
273278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                new SpannableString(createAddressText(
27330e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                                        replacement.getEntry()).trim() + " ");
273478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        displayText.setSpan(replacement, 0,
273578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                displayText.length() - 1,
273678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
273778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // Replace the old text we found with with the new display
273878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // text, which now may also contain the display name of the
273978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // recipient.
27400e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                        text.replace(start, end, displayText);
274178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        replacement.setOriginalText(displayText.toString());
274278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        replacements.set(i, null);
274378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
274478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        recipients.set(i, replacement);
274578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                    }
274678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                }
274778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            }
274878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            i++;
274978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                        }
27500e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                        setText(text);
275178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    }
275278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                };
275378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
275478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                if (Looper.myLooper() == Looper.getMainLooper()) {
275578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    runnable.run();
275678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                } else {
275778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    mHandler.post(runnable);
275878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                }
275978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
276078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        }
27611852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    }
27621852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
276378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy    private class IndividualReplacementTask
2764194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            extends AsyncTask<ArrayList<DrawableRecipientChip>, Void, Void> {
27651852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        @Override
2766194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        protected Void doInBackground(ArrayList<DrawableRecipientChip>... params) {
27671852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // For each chip in the list, look up the matching contact.
27681852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // If there is a match, replace that chip with the matching
27691852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // chip.
2770194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final ArrayList<DrawableRecipientChip> originalRecipients = params[0];
277103cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira            ArrayList<String> addresses = new ArrayList<String>();
2772194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip;
27731852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2774f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                chip = originalRecipients.get(i);
2775f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                if (chip != null) {
277603cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira                    addresses.add(createAddressText(chip.getEntry()));
2777f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                }
27781852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
27797a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy            final BaseRecipientAdapter adapter = getAdapter();
27808af0d3b6f34e03c08c8e67be2190da01c59889daAndrew Sapperstein            adapter.getMatchingRecipients(addresses, new RecipientMatchCallback() {
278116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp
278216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                        @Override
278394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        public void matchesFound(Map<String, RecipientEntry> entries) {
2784194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            for (final DrawableRecipientChip temp : originalRecipients) {
278516923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                if (RecipientEntry.isCreatedRecipient(temp.getEntry()
278616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        .getContactId())
278716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        && getSpannable().getSpanStart(temp) != -1) {
278816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    // Replace this.
278976f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert                                    final RecipientEntry entry = createValidatedEntry(entries
279016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            .get(tokenizeAddress(temp.getEntry().getDestination())
279116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                                    .toLowerCase()));
279276f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert                                    if (entry != null) {
279316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        mHandler.post(new Runnable() {
279416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            @Override
279516923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            public void run() {
279676f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert                                                replaceChip(temp, entry);
279716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            }
279816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        });
279916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    }
280016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                }
28011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            }
280216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                        }
280316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp
280494fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        @Override
2805f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                        public void matchesNotFound(final Set<String> unfoundAddresses) {
280694fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                            // No action required
280794fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        }
280816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                    });
28091852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            return null;
28101852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
28111852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    }
28121d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
2813e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
2814e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    /**
2815e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
2816e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     * more chip across activity restarts/
2817e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     */
2818e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    private class MoreImageSpan extends ImageSpan {
2819e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        public MoreImageSpan(Drawable b) {
2820e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            super(b);
2821e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        }
2822e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    }
2823e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
28241d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
28251d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onDown(MotionEvent e) {
28261d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
28271d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
28281d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
28291d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
28301d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
28311d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
28321d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
28331d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
28341d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
28351d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
28361d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onLongPress(MotionEvent event) {
28371d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        if (mSelectedChip != null) {
28381d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira            return;
28391d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
28401d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        float x = event.getX();
28411d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        float y = event.getY();
28421650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int offset = putOffsetInRange(x, y);
2843194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip currentChip = findChip(offset);
28441d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        if (currentChip != null) {
2845e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            if (mDragEnabled) {
2846e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Start drag-and-drop for the selected chip.
2847e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                startDrag(currentChip);
2848e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            } else {
2849e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Copy the selected chip email address.
2850e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                showCopyDialog(currentChip.getEntry().getDestination());
2851e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            }
2852e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2853e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2854e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
28551650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    // The following methods are used to provide some functionality on older versions of Android
28561650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    // These methods were copied out of JB MR2's TextView
28571650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    /////////////////////////////////////////////////
28581650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int supportGetOffsetForPosition(float x, float y) {
28591650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        if (getLayout() == null) return -1;
28601650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int line = supportGetLineAtCoordinate(y);
28611650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int offset = supportGetOffsetAtCoordinate(line, x);
28621650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return offset;
28631650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28641650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
28651650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private float supportConvertToLocalHorizontalCoordinate(float x) {
28661650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x -= getTotalPaddingLeft();
28671650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        // Clamp the position to inside of the view.
28681650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x = Math.max(0.0f, x);
28691650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
28701650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x += getScrollX();
28711650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return x;
28721650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28731650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
28741650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int supportGetLineAtCoordinate(float y) {
28751650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y -= getTotalPaddingLeft();
28761650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        // Clamp the position to inside of the view.
28771650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y = Math.max(0.0f, y);
28781650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
28791650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y += getScrollY();
28801650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return getLayout().getLineForVertical((int) y);
28811650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28821650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
28831650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int supportGetOffsetAtCoordinate(int line, float x) {
28841650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x = supportConvertToLocalHorizontalCoordinate(x);
28851650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return getLayout().getOffsetForHorizontal(line, x);
28861650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28871650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    /////////////////////////////////////////////////
28881650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
2889e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2890e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Enables drag-and-drop for chips.
2891e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2892e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    public void enableDrag() {
2893e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        mDragEnabled = true;
2894e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2895e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2896e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2897e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Starts drag-and-drop for the selected chip.
2898e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2899194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private void startDrag(DrawableRecipientChip currentChip) {
2900e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        String address = currentChip.getEntry().getDestination();
2901e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
2902e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2903e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // Start drag mode.
2904e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
2905e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2906e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // Remove the current chip, so drag-and-drop will result in a move.
2907e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // TODO (phamm): consider readd this chip if it's dropped outside a target.
2908e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        removeChip(currentChip);
2909e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2910e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2911e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2912e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Handles drag event.
2913e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2914e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    @Override
2915e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    public boolean onDragEvent(DragEvent event) {
2916e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        switch (event.getAction()) {
2917e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DRAG_STARTED:
2918e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Only handle plain text drag and drop.
2919e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
2920e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DRAG_ENTERED:
2921e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                requestFocus();
2922e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return true;
2923e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DROP:
2924e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                handlePasteClip(event.getClipData());
2925e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return true;
2926e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2927e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        return false;
2928e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2929e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2930e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
29311db635b5a09c77a27fe17a9ce31c21a67458c2bcAndrew Sapperstein     * Drag shadow for a {@link DrawableRecipientChip}.
2932e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2933e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private final class RecipientChipShadow extends DragShadowBuilder {
2934194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        private final DrawableRecipientChip mChip;
2935e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2936194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        public RecipientChipShadow(DrawableRecipientChip chip) {
2937e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            mChip = chip;
2938e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2939e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2940e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        @Override
2941e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
2942f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            Rect rect = mChip.getBounds();
2943e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            shadowSize.set(rect.width(), rect.height());
2944e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            shadowTouchPoint.set(rect.centerX(), rect.centerY());
2945e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2946e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2947e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        @Override
2948e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        public void onDrawShadow(Canvas canvas) {
2949f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            mChip.draw(canvas);
29501d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
29511d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29521d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29531d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private void showCopyDialog(final String address) {
2954fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        if (!mAttachedToWindow) {
2955fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler            return;
2956fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        }
29571d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyAddress = address;
29581d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setTitle(address);
295976e62e334ef2b9ec55e5395f8374072f7ee1ea84Mindy Pereira        mCopyDialog.setContentView(R.layout.copy_chip_dialog_layout);
29601d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setCancelable(true);
29611d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
296280f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        Button button = (Button)mCopyDialog.findViewById(android.R.id.button1);
296380f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        button.setOnClickListener(this);
296480f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        int btnTitleId;
2965c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor        if (isPhoneQuery()) {
296680f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor            btnTitleId = R.string.copy_number;
296780f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        } else {
296880f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor            btnTitleId = R.string.copy_email;
296980f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        }
297080f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        String buttonTitle = getContext().getResources().getString(btnTitleId);
297180f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        button.setText(buttonTitle);
29721d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setOnDismissListener(this);
29731d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.show();
29741d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29751d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29761d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29771d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
29781d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
29791d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
29801d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29811d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29821d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29831d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onShowPress(MotionEvent e) {
29841d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
29851d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29861d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29871d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29881d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
29891d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
29901d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
29911d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29921d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29931d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29941d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onDismiss(DialogInterface dialog) {
29951d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyAddress = null;
29961d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29971d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29981d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29991d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onClick(View v) {
30001d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Copy this to the clipboard.
30011d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
30021d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira                Context.CLIPBOARD_SERVICE);
30031d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
30041d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.dismiss();
30051d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
3006c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor
3007c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor    protected boolean isPhoneQuery() {
3008ca5227122474fb124a4fe573a2f07e14d7a33f3eMindy Pereira        return getAdapter() != null
3009858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy                && getAdapter().getQueryType() == BaseRecipientAdapter.QUERY_TYPE_PHONE;
3010858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy    }
3011858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy
3012858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy    @Override
3013858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy    public BaseRecipientAdapter getAdapter() {
3014858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy        return (BaseRecipientAdapter) super.getAdapter();
3015c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor    }
3016972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang
3017972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang    /**
3018972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang     * Append a new {@link RecipientEntry} to the end of the recipient chips, leaving any
3019972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang     * unfinished text at the end.
3020972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang     */
3021972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang    public void appendRecipientEntry(final RecipientEntry entry) {
3022972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        clearComposingText();
3023972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang
3024972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        final Editable editable = getText();
3025972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        int chipInsertionPoint = 0;
3026972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang
3027972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        // Find the end of last chip and see if there's any unchipified text.
3028972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        final DrawableRecipientChip[] recips = getSortedRecipients();
3029972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        if (recips != null && recips.length > 0) {
3030972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang            final DrawableRecipientChip last = recips[recips.length - 1];
3031972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang            // The chip will be inserted at the end of last chip + 1. All the unfinished text after
3032972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang            // the insertion point will be kept untouched.
3033972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang            chipInsertionPoint = editable.getSpanEnd(last) + 1;
3034972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        }
3035972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang
3036972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        final CharSequence chip = createChip(entry, false);
3037972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        if (chip != null) {
3038972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang            editable.insert(chipInsertionPoint, chip);
3039972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang        }
3040972def7a06f7d820313228c5b1311ccbe8096be0Kaikai Wang    }
304135e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao
304235e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao    private static class ChipBitmapContainer {
304335e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        Bitmap bitmap;
304435e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        // information used for positioning the loaded icon
304535e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        boolean loadIcon = true;
304635e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        float left;
304735e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        float top;
304835e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        float right;
304935e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao        float bottom;
305035e82d4f9522906f7953667cf5c5f8137ec2f5acJin Cao    }
30511852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira}
3052