RecipientEditTextView.java revision 9b398e3019d1e12c661647a277a338975a50d952
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.Collection;
9576f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Collections;
9676f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Comparator;
9776f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.HashSet;
9876f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.List;
9976f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Map;
10076f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.Set;
10176f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.regex.Matcher;
10276f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albertimport java.util.regex.Pattern;
10376f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert
1049159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira/**
1059159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * RecipientEditTextView is an auto complete text view for use with applications
1069159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira * that use the new Chips UI for addressing a message to recipients.
1079159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira */
10895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements
1091d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
110ecee50cc64d17d3cf7553a492dbf22f99f08aa56Mindy Pereira        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
1116a6bc7010f0f634ad32312d9802c50ed749f12bbMindy Pereira        TextView.OnEditorActionListener {
112cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
113aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_COMMA = ',';
114aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
115aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_SEMICOLON = ';';
116aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
117aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final char COMMIT_CHAR_SPACE = ' ';
118aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
11903e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy    private static final String SEPARATOR = String.valueOf(COMMIT_CHAR_COMMA)
12003e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy            + String.valueOf(COMMIT_CHAR_SPACE);
12103e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy
122cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private static final String TAG = "RecipientEditTextView";
123cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
124aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static int DISMISS = "dismiss".hashCode();
125aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
126aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static final long DISMISS_DELAY = 300;
127aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
12812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    // TODO: get correct number/ algorithm from with UX.
129f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
130f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ static final int CHIP_LIMIT = 2;
131f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
132f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private static final int MAX_CHIPS_PARSED = 50;
13312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
134aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private static int sSelectedTextColor = -1;
135aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
136aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Resources for displaying chips.
137cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Drawable mChipBackground = null;
138cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
139cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Drawable mChipDelete = null;
140cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
141aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Drawable mInvalidChipBackground;
142aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
143aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Drawable mChipBackgroundPressed;
144aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
145aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private float mChipHeight;
146aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
147aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private float mChipFontSize;
148aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
149ffd270a55d80db66bd6a1d7b786443fdc00af372Mindy Pereira    private float mLineSpacingExtra;
150ffd270a55d80db66bd6a1d7b786443fdc00af372Mindy Pereira
151cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private int mChipPadding;
152cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
153b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
154b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Enumerator for avatar position. See attr.xml for more details.
155c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * 0 for end, 1 for start.
156b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
157b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private int mAvatarPosition;
158b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
159c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    private static final int AVATAR_POSITION_END = 0;
160b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
161c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    private static final int AVATAR_POSITION_START = 1;
162b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
1639b398e3019d1e12c661647a277a338975a50d952Kevin Lin    /**
1649b398e3019d1e12c661647a277a338975a50d952Kevin Lin     * Enumerator for image span alignment. See attr.xml for more details.
1659b398e3019d1e12c661647a277a338975a50d952Kevin Lin     * 0 for bottom, 1 for baseline.
1669b398e3019d1e12c661647a277a338975a50d952Kevin Lin     */
1679b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private int mImageSpanAlignment;
1689b398e3019d1e12c661647a277a338975a50d952Kevin Lin
1699b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private static final int IMAGE_SPAN_ALIGNMENT_BOTTOM = 0;
1709b398e3019d1e12c661647a277a338975a50d952Kevin Lin
1719b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private static final int IMAGE_SPAN_ALIGNMENT_BASELINE = 1;
1729b398e3019d1e12c661647a277a338975a50d952Kevin Lin
1739b398e3019d1e12c661647a277a338975a50d952Kevin Lin
174b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private boolean mDisableDelete;
175b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
176cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private Tokenizer mTokenizer;
177cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
178aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private Validator mValidator;
179cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
180194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private DrawableRecipientChip mSelectedChip;
181cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1822bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    private Bitmap mDefaultContactPhoto;
1832bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
18412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private ImageSpan mMoreChip;
18512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
186750e6e52d9e560d5fbf687f15bf388a947e98eb2Mindy Pereira    private TextView mMoreItem;
18712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
18820c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    // VisibleForTesting
18920c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    final ArrayList<String> mPendingChips = new ArrayList<String>();
190f97eb41a7946f2c3013ac74f2451b78070531125Mindy Pereira
191007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira    private Handler mHandler;
192007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira
19302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    private int mPendingChipsCount = 0;
19402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
195f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private boolean mNoChips = false;
196f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
19795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private ListPopupWindow mAlternatesPopup;
19895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
19901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    private ListPopupWindow mAddressPopup;
20001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
20120c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    // VisibleForTesting
202194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    ArrayList<DrawableRecipientChip> mTemporaryRecipients;
2031852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
204194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private ArrayList<DrawableRecipientChip> mRemovedSpans;
2053bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
206076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    private boolean mShouldShrink = true;
207076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira
2081d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    // Chip copy fields.
2091d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private GestureDetector mGestureDetector;
2101d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
2111d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private Dialog mCopyDialog;
2121d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
2131d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private String mCopyAddress;
2141d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
21595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
216aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
217aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler     * selected chip.
21895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
21995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    private OnItemClickListener mAlternatesListener;
22095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
221c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private int mCheckedItem;
222aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
22379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private TextWatcher mTextWatcher;
22479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
225aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Obtain the enclosing scroll view, if it exists, so that the view can be
226aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // scrolled to show the last line of chips content.
227c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private ScrollView mScrollView;
228c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
229aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    private boolean mTriedGettingScrollView;
230c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
231e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private boolean mDragEnabled = false;
232e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2334491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor    // This pattern comes from android.util.Patterns. It has been tweaked to handle a "1" before
2344491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor    // parens, so numbers such as "1 (425) 222-2342" match.
2354491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor    private static final Pattern PHONE_PATTERN
2364491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor        = Pattern.compile(                                  // sdd = space, dot, or dash
2374491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor                "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
2384491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor                + "(1?[ ]*\\([0-9]+\\)[\\- \\.]*)?"         // 1(<digits>)<sdd>*
2394491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
2404491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor
24179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private final Runnable mAddTextWatcher = new Runnable() {
24279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
24379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void run() {
24479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (mTextWatcher == null) {
24579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                mTextWatcher = new RecipientTextWatcher();
24679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                addTextChangedListener(mTextWatcher);
24779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
24879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
24979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    };
25079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
2511852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private IndividualReplacementTask mIndividualReplacements;
2521852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
2532cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    private Runnable mHandlePendingChips = new Runnable() {
2542cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
2552cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        @Override
2562cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        public void run() {
2572cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            handlePendingChips();
2582cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
2592cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
2602cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    };
2612cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
262d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    private Runnable mDelayedShrink = new Runnable() {
263d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
264d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        @Override
265d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        public void run() {
266d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            shrink();
267d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        }
268d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
269d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    };
270d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
2715e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp    private int mMaxLines;
2725e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp
273093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    private static int sExcessTopPadding = -1;
274093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
275093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    private int mActionBarHeight;
276093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
277fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    private boolean mAttachedToWindow;
278fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler
279b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private DropdownChipLayouter mDropdownChipLayouter;
280b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
2819159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    public RecipientEditTextView(Context context, AttributeSet attrs) {
2829159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira        super(context, attrs);
28322faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        setChipDimensions(context, attrs);
284e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        if (sSelectedTextColor == -1) {
285e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
286e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        }
28795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        mAlternatesPopup = new ListPopupWindow(context);
28801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        mAddressPopup = new ListPopupWindow(context);
289bfedc1e199e57dcda494389fdca0750e1f165135Mindy Pereira        mCopyDialog = new Dialog(context);
29095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        mAlternatesListener = new OnItemClickListener() {
29195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            @Override
29295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            public void onItemClick(AdapterView<?> adapterView,View view, int position,
29395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                    long rowId) {
29421cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                mAlternatesPopup.setOnItemClickListener(null);
29595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
29695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        .getRecipientEntry(position));
29795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Message delayed = Message.obtain(mHandler, DISMISS);
29821cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                delayed.obj = mAlternatesPopup;
29995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
30095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                clearComposingText();
30195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
30295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        };
303e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
304b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira        setOnItemClickListener(this);
305fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        setCustomSelectionActionModeCallback(this);
306007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira        mHandler = new Handler() {
307007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            @Override
308007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            public void handleMessage(Message msg) {
309007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                if (msg.what == DISMISS) {
31095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                    ((ListPopupWindow) msg.obj).dismiss();
311007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                    return;
312007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                }
313007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira                super.handleMessage(msg);
314007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira            }
315007a76baab414c9d432d31c661668b1bd07e3f80Mindy Pereira        };
3164e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        mTextWatcher = new RecipientTextWatcher();
3174e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        addTextChangedListener(mTextWatcher);
3181d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mGestureDetector = new GestureDetector(context, this);
31993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        setOnEditorActionListener(this);
320b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
321b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        setDropdownChipLayouter(new DropdownChipLayouter(LayoutInflater.from(context), context));
322b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
323b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
324b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    protected void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
325b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        mDropdownChipLayouter = dropdownChipLayouter;
32693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    }
32793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira
32893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    @Override
329fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    protected void onDetachedFromWindow() {
330fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        mAttachedToWindow = false;
331fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    }
332fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler
333fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    @Override
334fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    protected void onAttachedToWindow() {
335fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        mAttachedToWindow = true;
336fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    }
337fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler
338fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler    @Override
33993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) {
34093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        if (action == EditorInfo.IME_ACTION_DONE) {
34193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            if (commitDefault()) {
34293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                return true;
34393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            }
34493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            if (mSelectedChip != null) {
34593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                clearSelectedChip();
34693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                return true;
34793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            } else if (focusNext()) {
34893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira                return true;
34993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            }
35093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        }
35193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        return false;
35293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    }
35393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira
35493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    @Override
35593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
35693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        InputConnection connection = super.onCreateInputConnection(outAttrs);
35793364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        int imeActions = outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION;
35893364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        if ((imeActions&EditorInfo.IME_ACTION_DONE) != 0) {
35993364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            // clear the existing action
36093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            outAttrs.imeOptions ^= imeActions;
36193364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            // set the DONE action
36293364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
36393364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        }
36493364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
36593364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira            outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
36693364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        }
3671af243fdc3b87979252bddc06097c5056ef93894Scott Kennedy
3681af243fdc3b87979252bddc06097c5056ef93894Scott Kennedy        outAttrs.actionId = EditorInfo.IME_ACTION_DONE;
369a98b8e49be8e1ff1938ab204d86372e292c78194Mindy Pereira        outAttrs.actionLabel = getContext().getString(R.string.done);
37093364a61b0dcd90e19b96ea9b5900a77e2c4902eMindy Pereira        return connection;
371e33555f13a9b05d835cb860e2c30ef40af3c8502Erik    }
3727afe160db4a48f66c964ced89e29e0b63b23c7c1Mindy Pereira
373194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /*package*/ DrawableRecipientChip getLastChip() {
374194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip last = null;
375194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips = getSortedRecipients();
376aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (chips != null && chips.length > 0) {
377aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            last = chips[chips.length - 1];
378e33555f13a9b05d835cb860e2c30ef40af3c8502Erik        }
379aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        return last;
380fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    }
381fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira
382fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    @Override
383fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    public void onSelectionChanged(int start, int end) {
384fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // When selection changes, see if it is inside the chips area.
385fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // If so, move the cursor back after the chips again.
386194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip last = getLastChip();
387f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (last != null && start < getSpannable().getSpanEnd(last)) {
388aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            // Grab the last chip and set the cursor to after it.
389aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
39005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
39105dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        super.onSelectionChanged(start, end);
39205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    }
39305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira
394dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    @Override
395dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    public void onRestoreInstanceState(Parcelable state) {
396dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        if (!TextUtils.isEmpty(getText())) {
397dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            super.onRestoreInstanceState(null);
398dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        } else {
399dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            super.onRestoreInstanceState(state);
400dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        }
401dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    }
402dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira
403aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    @Override
404daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira    public Parcelable onSaveInstanceState() {
405daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        // If the user changes orientation while they are editing, just roll back the selection.
406daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        clearSelectedChip();
407daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira        return super.onSaveInstanceState();
408daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira    }
409daa640c82bfa8b1f81d986d7109029d2fdec263dMindy Pereira
41002a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    /**
41102a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * Convenience method: Append the specified text slice to the TextView's
41202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * display buffer, upgrading it to BufferType.EDITABLE if it was
41302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * not already editable. Commas are excluded as they are added automatically
41402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     * by the view.
41502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira     */
41602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    @Override
41702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    public void append(CharSequence text, int start, int end) {
418e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        // We don't care about watching text changes while appending.
419e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        if (mTextWatcher != null) {
420e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            removeTextChangedListener(mTextWatcher);
421e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira        }
42202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        super.append(text, start, end);
42302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
4240124468e712b4098ea240b8d45f84e52826b293dmindyp            String displayString = text.toString();
42503e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy
42603e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy            if (!displayString.trim().endsWith(String.valueOf(COMMIT_CHAR_COMMA))) {
42703e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                // We have no separator, so we should add it
42803e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                super.append(SEPARATOR, 0, SEPARATOR.length());
42903e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                displayString += SEPARATOR;
4300124468e712b4098ea240b8d45f84e52826b293dmindyp            }
43103e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy
43220d7af71188a4a2d94c8d9edd7eff7879d6df4c4mindyp            if (!TextUtils.isEmpty(displayString)
43302a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                    && TextUtils.getTrimmedLength(displayString) > 0) {
43402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                mPendingChipsCount++;
43503e9e9833bced880a5e29bb06598d98dfa8d686eScott Kennedy                mPendingChips.add(displayString);
43602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira            }
43702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        }
438abb864d610bdd171d6b0dfd2e83648952155e6ebmindyp        // Put a message on the queue to make sure we ALWAYS handle pending
439abb864d610bdd171d6b0dfd2e83648952155e6ebmindyp        // chips.
4402cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        if (mPendingChipsCount > 0) {
4412cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            postHandlePendingChips();
4422cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
4434e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        mHandler.post(mAddTextWatcher);
44402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    }
44502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
44605dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    @Override
44705dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
448d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira        super.onFocusChanged(hasFocus, direction, previous);
44905dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        if (!hasFocus) {
45012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            shrink();
451fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        } else {
45212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            expand();
453fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        }
4549159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
4559159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
456093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    private int getExcessTopPadding() {
457093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (sExcessTopPadding == -1) {
458e7f7a7d521b6b369035370b6c9241d2c6e313b44mindyp            sExcessTopPadding = (int) (mChipHeight + mLineSpacingExtra);
459093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        }
460093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        return sExcessTopPadding;
461093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    }
462093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
4636b7110f320c978c368c28bdb06212c6a6df12f1fAlice Yang    @Override
464093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
465093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        super.setAdapter(adapter);
466b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        BaseRecipientAdapter baseAdapter = (BaseRecipientAdapter) adapter;
467b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        baseAdapter.registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
468b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            @Override
469b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            public void onChanged(List<RecipientEntry> entries) {
470b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                // Scroll the chips field to the top of the screen so
471b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                // that the user can see as many results as possible.
472b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                if (entries != null && entries.size() > 0) {
473b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                    scrollBottomIntoView();
474b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                }
475b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            }
476b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        });
477b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        baseAdapter.setDropdownChipLayouter(mDropdownChipLayouter);
478093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    }
479093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
48053e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin    protected void scrollBottomIntoView() {
481093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (mScrollView != null && mShouldShrink) {
482093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int[] location = new int[2];
483093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            getLocationOnScreen(location);
484093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int height = getHeight();
485093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int currentPos = location[1] + height;
486093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            // Desired position shows at least 1 line of chips below the action
487093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            // bar. We add excess padding to make sure this is always below other
488093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            // content.
489093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            int desiredPos = (int) mChipHeight + mActionBarHeight + getExcessTopPadding();
490093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            if (currentPos > desiredPos) {
491093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp                mScrollView.scrollBy(0, currentPos - desiredPos);
492093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            }
493093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        }
494093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp    }
495093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp
49653e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin    protected ScrollView getScrollView() {
4977a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy        return mScrollView;
49853e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin    }
49953e718a52258e2240ed82faaee0ecef05fe112a0Kevin Lin
500d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    @Override
501d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    public void performValidation() {
502d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira        // Do nothing. Chips handles its own validation.
503d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira    }
504d5d86aafdbb1487ade3ecf70b92c00d20b94f9c8Mindy Pereira
50512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private void shrink() {
5065c125afa54288595524c85182031421cbad08ac3Mindy Pereira        if (mTokenizer == null) {
5075c125afa54288595524c85182031421cbad08ac3Mindy Pereira            return;
5085c125afa54288595524c85182031421cbad08ac3Mindy Pereira        }
5095e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        long contactId = mSelectedChip != null ? mSelectedChip.getEntry().getContactId() : -1;
5105e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (mSelectedChip != null && contactId != RecipientEntry.INVALID_CONTACT
5115e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                && (!isPhoneQuery() && contactId != RecipientEntry.GENERATED_CONTACT)) {
51212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            clearSelectedChip();
51312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        } else {
514d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            if (getWidth() <= 0) {
515d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // We don't have the width yet which means the view hasn't been drawn yet
516d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // and there is no reason to attempt to commit chips yet.
517d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // This focus lost must be the result of an orientation change
518d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // or an initial rendering.
519d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                // Re-post the shrink for later.
520d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                mHandler.removeCallbacks(mDelayedShrink);
521d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                mHandler.post(mDelayedShrink);
522d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                return;
523d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            }
524e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            // Reset any pending chips as they would have been handled
525e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            // when the field lost focus.
526e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            if (mPendingChipsCount > 0) {
5272cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                postHandlePendingChips();
528e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            } else {
529e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                Editable editable = getText();
530e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                int end = getSelectionEnd();
531e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                int start = mTokenizer.findTokenStart(editable, end);
532194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] chips =
533194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        getSpannable().getSpans(start, end, DrawableRecipientChip.class);
534e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                if ((chips == null || chips.length == 0)) {
535d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    Editable text = getText();
536d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    int whatEnd = mTokenizer.findTokenEnd(text, start);
537d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    // This token was already tokenized, so skip past the ending token.
538448e90b97a4df8102d6e1d2039274d9ea188dff9Mindy Pereira                    if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
5394f82d888c680a61b95373740ce68bfb48a242617mindyp                        whatEnd = movePastTerminators(whatEnd);
540d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    }
541e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    // In the middle of chip; treat this as an edit
542e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    // and commit the whole token.
543d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira                    int selEnd = getSelectionEnd();
5446337231a660b717cd6f5c40d524f4aabfcc865b0Mindy Pereira                    if (whatEnd != selEnd) {
545e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                        handleEdit(start, whatEnd);
546e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    } else {
547e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                        commitChip(start, end, editable);
548e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                    }
549e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira                }
550a74f40dc1073117349d1d39b7c8396a39d24f57fMindy Pereira            }
551e81f8a3d2c9bc916a72b3bf42f56c3ef8fb547f3Mindy Pereira            mHandler.post(mAddTextWatcher);
55212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
5533bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        createMoreChip();
55412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
55512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
55612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    private void expand() {
557374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp        if (mShouldShrink) {
558374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp            setMaxLines(Integer.MAX_VALUE);
559374ccb2935ea793d2fbee54ec5fe6d71af934d27mindyp        }
56012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        removeMoreChip();
56112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        setCursorVisible(true);
56212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        Editable text = getText();
56312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        setSelection(text != null && text.length() > 0 ? text.length() : 0);
5641852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // If there are any temporary chips, try replacing them now that the user
5651852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // has expanded the field.
5661852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
5671852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            new RecipientReplacementTask().execute();
5681852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            mTemporaryRecipients = null;
5691852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
57012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
57112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
5722bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
5736e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira        paint.setTextSize(mChipFontSize);
57402a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
57502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira            Log.d(TAG, "Max width is negative: " + maxWidth);
57602a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        }
57702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        return TextUtils.ellipsize(text, paint, maxWidth,
57802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira                TextUtils.TruncateAt.END);
5792bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
580cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
581b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
582b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Creates a bitmap of the given contact on a selected chip.
583b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     *
584b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param contact The recipient entry to pull data from.
585b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param paint The paint to use to draw the bitmap.
586b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
587f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint) {
588b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        paint.setColor(sSelectedTextColor);
589b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Bitmap photo;
590b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (mDisableDelete) {
591b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            // Show the avatar instead if we don't want to delete
592b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            photo = getAvatarIcon(contact);
5932bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        } else {
594b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            photo = ((BitmapDrawable) mChipDelete).getBitmap();
5952bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
596b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return createChipBitmap(contact, paint, photo, mChipBackgroundPressed);
5972bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
598cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
599b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
600b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Creates a bitmap of the given contact on a selected chip.
601b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     *
602b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param contact The recipient entry to pull data from.
603b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * @param paint The paint to use to draw the bitmap.
604b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
605b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    // TODO: Is leaveBlankIconSpacer obsolete now that we have left and right attributes?
606f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
6075e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            boolean leaveBlankIconSpacer) {
608b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Drawable background = getChipBackground(contact);
609b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Bitmap photo = getAvatarIcon(contact);
610b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        paint.setColor(getContext().getResources().getColor(android.R.color.black));
611b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return createChipBitmap(contact, paint, photo, background);
612b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
613b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
614b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private Bitmap createChipBitmap(RecipientEntry contact, TextPaint paint, Bitmap icon,
615b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Drawable background) {
616b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (background == null) {
617b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
618c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein            return Bitmap.createBitmap(
619c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                    (int) mChipHeight * 2, (int) mChipHeight, Bitmap.Config.ARGB_8888);
620b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        }
621b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
622b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Rect backgroundPadding = new Rect();
623b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        background.getPadding(backgroundPadding);
624b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
625cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Ellipsize the text so that it takes AT MOST the entire width of the
626cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // autocomplete text entry area. Make sure to leave space for padding
627cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // on the sides.
6286e8e8e8165a797611f80a2c17249147333d55ea7Mindy Pereira        int height = (int) mChipHeight;
629b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Since the icon is a square, it's width is equal to the maximum height it can be inside
630b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // the chip.
631b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        int iconWidth = height - backgroundPadding.top - backgroundPadding.bottom;
6326ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        float[] widths = new float[1];
6336ed7ded9deabbc92ed8341cf922673fd4626ba7eMindy Pereira        paint.getTextWidths(" ", widths);
634aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
6359b398e3019d1e12c661647a277a338975a50d952Kevin Lin                calculateAvailableWidth() - iconWidth - widths[0] - backgroundPadding.left
6369b398e3019d1e12c661647a277a338975a50d952Kevin Lin                    - backgroundPadding.right);;
637b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        int textWidth = (int) paint.measureText(ellipsizedText, 0, ellipsizedText.length());
638b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
6399d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira        // Make sure there is a minimum chip width so the user can ALWAYS
6409d2a1980bbcad5dae3b0fb03c35208724b377fa8Mindy Pereira        // tap a chip without difficulty.
641b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        int width = Math.max(iconWidth * 2, textWidth + (mChipPadding * 2) + iconWidth
642b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                + backgroundPadding.left + backgroundPadding.right);
643cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
644cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Create the background of the chip.
645cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
646cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Canvas canvas = new Canvas(tmpBitmap);
6476b6de6266d3bede33728cf995f1fd5c59ec5a55dMindy Pereira
648b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Draw the background drawable
649b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        background.setBounds(0, 0, width, height);
650b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        background.draw(canvas);
651b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Draw the text vertically aligned
652c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        int textX = shouldPositionAvatarOnRight() ?
653b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                mChipPadding + backgroundPadding.left :
654b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                width - backgroundPadding.right - mChipPadding - textWidth;
655b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
656b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                textX, getTextYOffset(ellipsizedText.toString(), paint, height), paint);
657b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (icon != null) {
658b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            // Draw the icon
659c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein            int iconX = shouldPositionAvatarOnRight() ?
660b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                    width - backgroundPadding.right - iconWidth :
661b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                    backgroundPadding.left;
662b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            RectF src = new RectF(0, 0, icon.getWidth(), icon.getHeight());
663b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            RectF dst = new RectF(iconX,
664b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                    0 + backgroundPadding.top,
665b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                    iconX + iconWidth,
666b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                    height - backgroundPadding.bottom);
667b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            drawIconOnCanvas(icon, canvas, paint, src, dst);
6682bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
6692bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        return tmpBitmap;
6702bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira    }
671cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
672aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /**
673c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * Returns true if the avatar should be positioned at the right edge of the chip.
674c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * Takes into account both the set avatar position (start or end) as well as whether
675c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     * the layout direction is LTR or RTL.
676c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein     */
677c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    private boolean shouldPositionAvatarOnRight() {
678c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ?
679c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                getLayoutDirection() == LAYOUT_DIRECTION_RTL : false;
680c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        final boolean assignedPosition = mAvatarPosition == AVATAR_POSITION_END;
681c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        // If in Rtl mode, the position should be flipped.
682c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein        return isRtl ? !assignedPosition : assignedPosition;
683c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    }
684c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein
685c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein    /**
686b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Returns the avatar icon to use for this recipient entry. Returns null if we don't want to
687b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * draw an icon for this recipient.
688b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
689b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    private Bitmap getAvatarIcon(RecipientEntry contact) {
690b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        // Don't draw photos for recipients that have been typed in OR generated on the fly.
691b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        long contactId = contact.getContactId();
692b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        boolean drawPhotos = isPhoneQuery() ?
693b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                contactId != RecipientEntry.INVALID_CONTACT
694b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                : (contactId != RecipientEntry.INVALID_CONTACT
695b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                        && (contactId != RecipientEntry.GENERATED_CONTACT &&
696b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                                !TextUtils.isEmpty(contact.getDisplayName())));
697b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
698b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (drawPhotos) {
699b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            byte[] photoBytes = contact.getPhotoBytes();
700b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            // There may not be a photo yet if anything but the first contact address
701b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            // was selected.
702b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
703b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                // TODO: cache this in the recipient entry?
704b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                getAdapter().fetchPhoto(contact, contact.getPhotoThumbnailUri());
705b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                photoBytes = contact.getPhotoBytes();
706b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            }
707b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            if (photoBytes != null) {
708b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                return BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
709b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            } else {
710b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                // TODO: can the scaled down default photo be cached?
711b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                return mDefaultContactPhoto;
712b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            }
713b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        }
714b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
715b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return null;
716b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
717b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
718b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
719aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira     * Get the background drawable for a RecipientChip.
720aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira     */
721aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
722f30a42800318f6790d55421f8f6980eb38db4d3cmindyp    /* package */Drawable getChipBackground(RecipientEntry contact) {
723f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return contact.isValid() ? mChipBackground : mInvalidChipBackground;
724aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    }
725aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
726b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
727b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * Given a height, returns a Y offset that will draw the text in the middle of the height.
728b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
729b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    protected float getTextYOffset(String text, TextPaint paint, int height) {
7301d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        Rect bounds = new Rect();
731e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        paint.getTextBounds(text, 0, text.length(), bounds);
7328ff03734c31831d3d60b0cb2707c006dbf64d330Mindy Pereira        int textHeight = bounds.bottom - bounds.top ;
7338ff03734c31831d3d60b0cb2707c006dbf64d330Mindy Pereira        return height - ((height - textHeight) / 2) - (int)paint.descent();
7341d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira    }
7351d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira
736b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /**
737b8985b7b595e38518d2a0657b89ff47bd34862abScott Kennedy     * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination
738b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     * rectangle of the canvas.
739b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin     */
740b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    protected void drawIconOnCanvas(Bitmap icon, Canvas canvas, Paint paint, RectF src, RectF dst) {
741b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        Matrix matrix = new Matrix();
742b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
743b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        canvas.drawBitmap(icon, matrix, paint);
744b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
745b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
746194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private DrawableRecipientChip constructChipSpan(RecipientEntry contact, boolean pressed,
7475e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            boolean leaveIconSpace) throws NullPointerException {
7482bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        if (mChipBackground == null) {
7492bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira            throw new NullPointerException(
7502bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira                    "Unable to render any chips as setChipDimensions was not called.");
751cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
75202a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
7532bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        TextPaint paint = getPaint();
7542bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        float defaultSize = paint.getTextSize();
755e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        int defaultColor = paint.getColor();
7562bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira
7572bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        Bitmap tmpBitmap;
7582bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        if (pressed) {
759f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy            tmpBitmap = createSelectedChip(contact, paint);
760cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
7612bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        } else {
762f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy            tmpBitmap = createUnselectedChip(contact, paint, leaveIconSpace);
7632bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        }
764cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
765cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Pass the full text, un-ellipsized, to the chip.
766cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
7672bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
7689b398e3019d1e12c661647a277a338975a50d952Kevin Lin        DrawableRecipientChip recipientChip =
7699b398e3019d1e12c661647a277a338975a50d952Kevin Lin                new VisibleRecipientChip(result, contact, getImageSpanAlignment());
770cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Return text to the original size.
771cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        paint.setTextSize(defaultSize);
772e50b0a1f168322390b63f435f222766cdae6ba7dMindy Pereira        paint.setColor(defaultColor);
773cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return recipientChip;
7749159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
7759159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
7769b398e3019d1e12c661647a277a338975a50d952Kevin Lin    private int getImageSpanAlignment() {
7779b398e3019d1e12c661647a277a338975a50d952Kevin Lin        switch (mImageSpanAlignment) {
7789b398e3019d1e12c661647a277a338975a50d952Kevin Lin            case IMAGE_SPAN_ALIGNMENT_BASELINE:
7799b398e3019d1e12c661647a277a338975a50d952Kevin Lin                return ImageSpan.ALIGN_BASELINE;
7809b398e3019d1e12c661647a277a338975a50d952Kevin Lin            case IMAGE_SPAN_ALIGNMENT_BOTTOM:
7819b398e3019d1e12c661647a277a338975a50d952Kevin Lin                return ImageSpan.ALIGN_BOTTOM;
7829b398e3019d1e12c661647a277a338975a50d952Kevin Lin            default:
7839b398e3019d1e12c661647a277a338975a50d952Kevin Lin                return ImageSpan.ALIGN_BOTTOM;
7849b398e3019d1e12c661647a277a338975a50d952Kevin Lin        }
7859b398e3019d1e12c661647a277a338975a50d952Kevin Lin    }
7869b398e3019d1e12c661647a277a338975a50d952Kevin Lin
787a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
788342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Calculate the bottom of the line the chip will be located on using:
789342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * 1) which line the chip appears on
79021625f86828b6bbfc5e87796564eaca5127155feMindy Pereira     * 2) the height of a chip
79121625f86828b6bbfc5e87796564eaca5127155feMindy Pereira     * 3) padding built into the edit text view
792a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
79381fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira    private int calculateOffsetFromBottom(int line) {
79421625f86828b6bbfc5e87796564eaca5127155feMindy Pereira        // Line offsets start at zero.
795c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        int actualLine = getLineCount() - (line + 1);
7961d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
7971d37d9775446acd8e4e6bb0637565e88b5360096Mindy Pereira                + getDropDownVerticalOffset();
798f621a601e1f966c89b7aadbcca384021e14d668dMindy Pereira    }
799f621a601e1f966c89b7aadbcca384021e14d668dMindy Pereira
800a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
801a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Get the max amount of space a chip can take up. The formula takes into
802a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * account the width of the EditTextView, any view padding, and padding
803a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * that will be added to the chip.
804a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
805f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private float calculateAvailableWidth() {
8062bfe48e4dda1e172f4c857a0ee1a99d924fb226fMindy Pereira        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
8079159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
8089159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
80952c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira
81022faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira    private void setChipDimensions(Context context, AttributeSet attrs) {
8115da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecipientEditTextView, 0,
8125da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                0);
81352c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira        Resources r = getContext().getResources();
8148c474ec629cca3cf4bf2c867f37513ae35e3ff72mindyp
81522faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        mChipBackground = a.getDrawable(R.styleable.RecipientEditTextView_chipBackground);
81622faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        if (mChipBackground == null) {
81722faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira            mChipBackground = r.getDrawable(R.drawable.chip_background);
81822faaa6f1eacb6ae69e4fd5d97aeeefc77973cd1Mindy Pereira        }
8195da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipBackgroundPressed = a
8205da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                .getDrawable(R.styleable.RecipientEditTextView_chipBackgroundPressed);
8215da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipBackgroundPressed == null) {
8225da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipBackgroundPressed = r.getDrawable(R.drawable.chip_background_selected);
8235da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8245da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipDelete = a.getDrawable(R.styleable.RecipientEditTextView_chipDelete);
8255da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipDelete == null) {
8265da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipDelete = r.getDrawable(R.drawable.chip_delete);
8275da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8285da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipPadding = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipPadding, -1);
8295da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipPadding == -1) {
8305da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
8315da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8325da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
8335da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
8345da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
83552c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.more_item, null);
8365da0234c9a7108d3386039816c7469753b79c307Mindy Pereira
8375da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipHeight = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipHeight, -1);
8385da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipHeight == -1) {
8395da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipHeight = r.getDimension(R.dimen.chip_height);
8405da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8415da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mChipFontSize = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipFontSize, -1);
8425da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mChipFontSize == -1) {
8435da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mChipFontSize = r.getDimension(R.dimen.chip_text_size);
8445da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
8455da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        mInvalidChipBackground = a
8465da0234c9a7108d3386039816c7469753b79c307Mindy Pereira                .getDrawable(R.styleable.RecipientEditTextView_invalidChipBackground);
8475da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        if (mInvalidChipBackground == null) {
8485da0234c9a7108d3386039816c7469753b79c307Mindy Pereira            mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
8495da0234c9a7108d3386039816c7469753b79c307Mindy Pereira        }
850b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        mAvatarPosition = a.getInt(R.styleable.RecipientEditTextView_avatarPosition, 0);
8519b398e3019d1e12c661647a277a338975a50d952Kevin Lin        mImageSpanAlignment = a.getInt(R.styleable.RecipientEditTextView_imageSpanAlignment, 0);
852b8985b7b595e38518d2a0657b89ff47bd34862abScott Kennedy        mDisableDelete = a.getBoolean(R.styleable.RecipientEditTextView_disableDelete, false);
853b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
854f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        mLineSpacingExtra =  r.getDimension(R.dimen.line_spacing_extra);
855f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        mMaxLines = r.getInteger(R.integer.chips_max_lines);
856093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        TypedValue tv = new TypedValue();
857093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
858093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
859093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp                    .getDisplayMetrics());
860093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        }
861b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
862ef79800ff65490ec91ce77f55c4ade142d1cc4b2Mindy Pereira        a.recycle();
86352c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira    }
86452c441e2c03e0f48572348953b985a4bf989c057Mindy Pereira
865d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
866d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ void setMoreItem(TextView moreItem) {
867d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira        mMoreItem = moreItem;
868d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    }
869d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira
87097cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
87197cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
87297cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    /* package */ void setChipBackground(Drawable chipBackground) {
87397cb25912dab282cf732757f68b0405ed005f00bMindy Pereira        mChipBackground = chipBackground;
87497cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    }
87597cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
87697cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
87797cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    /* package */ void setChipHeight(int height) {
87897cb25912dab282cf732757f68b0405ed005f00bMindy Pereira        mChipHeight = height;
87997cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    }
88097cb25912dab282cf732757f68b0405ed005f00bMindy Pereira
881b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    public float getChipHeight() {
882b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return mChipHeight;
883b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    }
884b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
885076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    /**
886076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * Set whether to shrink the recipients field such that at most
887076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * one line of recipients chips are shown when the field loses
888076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * focus. By default, the number of displayed recipients will be
889076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * limited and a "more" chip will be shown when focus is lost.
890076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     * @param shrink
891076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira     */
892076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    public void setOnFocusListShrinkRecipients(boolean shrink) {
893076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        mShouldShrink = shrink;
894076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira    }
895076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira
896cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
89702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    public void onSizeChanged(int width, int height, int oldw, int oldh) {
89802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira        super.onSizeChanged(width, height, oldw, oldh);
899db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (width != 0 && height != 0) {
900db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            if (mPendingChipsCount > 0) {
901db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                postHandlePendingChips();
902db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            } else {
903db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                checkChipWidths();
904db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            }
9058005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira        }
9061852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        // Try to find the scroll view parent, if it exists.
907aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (mScrollView == null && !mTriedGettingScrollView) {
908c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            ViewParent parent = getParent();
909c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            while (parent != null && !(parent instanceof ScrollView)) {
910c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                parent = parent.getParent();
911c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            }
912c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            if (parent != null) {
913c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                mScrollView = (ScrollView) parent;
914c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira            }
915aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            mTriedGettingScrollView = true;
916c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        }
91702a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    }
91802a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira
9192cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    private void postHandlePendingChips() {
9202cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        mHandler.removeCallbacks(mHandlePendingChips);
9212cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        mHandler.post(mHandlePendingChips);
9222cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira    }
9232cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira
924db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira    private void checkChipWidths() {
925db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        // Check the widths of the associated chips.
926194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips = getSortedRecipients();
927db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (chips != null) {
928db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            Rect bounds;
929194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            for (DrawableRecipientChip chip : chips) {
930f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                bounds = chip.getBounds();
931db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
932db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                    // Need to redraw that chip.
933db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                    replaceChip(chip, chip.getEntry());
934db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira                }
935db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            }
9362cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        }
937db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira    }
938db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira
939f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
940f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ void handlePendingChips() {
941f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (getViewWidth() <= 0) {
9428005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // The widget has not been sized yet.
9438005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // This will be called as a result of onSizeChanged
9448005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            // at a later point.
9458005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira            return;
9468005f42b51fdca5382ce3fd0b083e40900191da6Mindy Pereira        }
947db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        if (mPendingChipsCount <= 0) {
948db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira            return;
949db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira        }
950db1ca655cdbd92286b2f7dc942f504a50c491100Mindy Pereira
9512cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira        synchronized (mPendingChips) {
9522cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            Editable editable = getText();
9532cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            // Tokenize!
954f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
955f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                for (int i = 0; i < mPendingChips.size(); i++) {
956f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    String current = mPendingChips.get(i);
957f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    int tokenStart = editable.toString().indexOf(current);
958488718e8f464d8554dd1cb00395fdfc7871f0fc8mindyp                    // Always leave a space at the end between tokens.
959488718e8f464d8554dd1cb00395fdfc7871f0fc8mindyp                    int tokenEnd = tokenStart + current.length() - 1;
960f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    if (tokenStart >= 0) {
961f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        // When we have a valid token, include it with the token
962f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        // to the left.
963f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        if (tokenEnd < editable.length() - 2
964f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                                && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
965f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                            tokenEnd++;
966f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        }
967f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                        createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT
968f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                                || !mShouldShrink);
9692cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    }
970f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    mPendingChipsCount--;
9713bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                }
972f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                sanitizeEnd();
973f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            } else {
974f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                mNoChips = true;
9753bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
976f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
977dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
9782cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
9792cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
9802cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    new RecipientReplacementTask().execute();
9812cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    mTemporaryRecipients = null;
9822cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                } else {
9832cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    // Create the "more" chip
9842cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    mIndividualReplacements = new IndividualReplacementTask();
985194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    mIndividualReplacements.execute(new ArrayList<DrawableRecipientChip>(
9862cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                            mTemporaryRecipients.subList(0, CHIP_LIMIT)));
9875e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                    if (mTemporaryRecipients.size() > CHIP_LIMIT) {
988194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        mTemporaryRecipients = new ArrayList<DrawableRecipientChip>(
9895e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                                mTemporaryRecipients.subList(CHIP_LIMIT,
9905e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                                        mTemporaryRecipients.size()));
9915e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                    } else {
9925e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                        mTemporaryRecipients = null;
9935e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                    }
9942cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                    createMoreChip();
9952cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                }
9962cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            } else {
9972cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                // There are too many recipients to look up, so just fall back
998dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                // to showing addresses for all of them.
9992cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira                mTemporaryRecipients = null;
10001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                createMoreChip();
10011852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
10022cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            mPendingChipsCount = 0;
10032cac23112a97802d460bffe41fa80a939bf730a5Mindy Pereira            mPendingChips.clear();
10043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
10053bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
10063bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
1007f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1008f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ int getViewWidth() {
1009f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return getWidth();
1010f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1011f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
10123bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    /**
10133bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * Remove any characters after the last valid chip.
10143bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     */
1015aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
1016aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ void sanitizeEnd() {
10173c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        // Don't sanitize while we are waiting for pending chips to complete.
10183c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        if (mPendingChipsCount > 0) {
10193c42baf5b81810bce77e776963f20c960865e85bMindy Pereira            return;
10203c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        }
10213bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // Find the last chip; eliminate any commit characters after it.
1022194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips = getSortedRecipients();
1023f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        Spannable spannable = getSpannable();
10243bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        if (chips != null && chips.length > 0) {
10253bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            int end;
1026dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            mMoreChip = getMoreChip();
10273bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            if (mMoreChip != null) {
1028f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                end = spannable.getSpanEnd(mMoreChip);
10293bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            } else {
1030f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                end = getSpannable().getSpanEnd(getLastChip());
10313bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
10323bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            Editable editable = getText();
10333bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            int length = editable.length();
10343bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            if (length > end) {
10353bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                // See what characters occur after that and eliminate them.
10363bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                if (Log.isLoggable(TAG, Log.DEBUG)) {
10373bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
10383bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                            + editable);
10393bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                }
10403bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                editable.delete(end + 1, length);
10413bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            }
10423bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
10433bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
10443bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
10453bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    /**
10463bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * Create a chip that represents just the email address of a recipient. At some later
10473bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     * point, this chip will be attached to a real contact entry, if one exists.
10483bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira     */
104920c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    // VisibleForTesting
105020c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy    void createReplacementChip(int tokenStart, int tokenEnd, Editable editable,
1051f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            boolean visible) {
105232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        if (alreadyHasChip(tokenStart, tokenEnd)) {
105332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // There is already a chip present at this location.
105432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // Don't recreate it.
105532366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            return;
105632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        }
10577ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira        String token = editable.toString().substring(tokenStart, tokenEnd);
105820c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy        final String trimmedToken = token.trim();
105920c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy        int commitCharIndex = trimmedToken.lastIndexOf(COMMIT_CHAR_COMMA);
106041137c249b5fe547a3631d9c6db57ff980022affTom Taylor        if (commitCharIndex != -1 && commitCharIndex == trimmedToken.length() - 1) {
106120c8aa75e7213ee82089b9aea9f407ca62302167Scott Kennedy            token = trimmedToken.substring(0, trimmedToken.length() - 1);
10623bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
10633bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        RecipientEntry entry = createTokenizedEntry(token);
1064ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        if (entry != null) {
1065194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip = null;
1066ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            try {
1067f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (!mNoChips) {
1068f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    /*
1069f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                     * leave space for the contact icon if this is not just an
1070f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                     * email address
1071f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                     */
1072f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    boolean leaveSpace = TextUtils.isEmpty(entry.getDisplayName())
1073f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                            || TextUtils.equals(entry.getDisplayName(),
1074f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                                    entry.getDestination());
1075f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    chip = visible ?
1076f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                            constructChipSpan(entry, false, leaveSpace)
1077f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                            : new InvisibleRecipientChip(entry);
1078f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
1079ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            } catch (NullPointerException e) {
1080ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1081ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
1082cb76b4d2bcb7b72b9505a620ca71f753506e48b9mindyp            editable.setSpan(chip, tokenStart, tokenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1083ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            // Add this chip to the list of entries "to replace"
1084ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            if (chip != null) {
1085dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                if (mTemporaryRecipients == null) {
1086194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    mTemporaryRecipients = new ArrayList<DrawableRecipientChip>();
1087dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                }
10880613ff859a6b44685af62821eac369597cf69b26Scott Kennedy                chip.setOriginalText(token);
1089ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                mTemporaryRecipients.add(chip);
1090ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
10911852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
10923bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
10933bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
1094abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor    private static boolean isPhoneNumber(String number) {
1095abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        // TODO: replace this function with libphonenumber's isPossibleNumber (see
1096abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        // PhoneNumberUtil). One complication is that it requires the sender's region which
1097abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        // comes from the CurrentCountryIso. For now, let's just do this simple match.
1098abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        if (TextUtils.isEmpty(number)) {
1099abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            return false;
1100abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        }
1101abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor
11024491a0679fbf4472f9bc199197c1d4abe6a9d969Tom Taylor        Matcher match = PHONE_PATTERN.matcher(number);
1103abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        return match.matches();
1104abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor    }
1105abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor
1106983c99b9e00913e8d2a5642cd05ccb9a81570be7Scott Kennedy    // VisibleForTesting
1107983c99b9e00913e8d2a5642cd05ccb9a81570be7Scott Kennedy    RecipientEntry createTokenizedEntry(final String token) {
1108ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        if (TextUtils.isEmpty(token)) {
1109ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            return null;
1110ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira        }
11115e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (isPhoneQuery() && isPhoneNumber(token)) {
11120ba9133c904b8c35af8209a54604331cd671bc1fTom Taylor            return RecipientEntry.constructFakePhoneEntry(token, true);
11135e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        }
11143bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
111501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        String display = null;
1116f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        boolean isValid = isValid(token);
1117f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        if (isValid && tokens != null && tokens.length > 0) {
1118ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira            // If we can get a name from tokenizing, then generate an entry from
1119ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira            // this.
112001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            display = tokens[0].getName();
112101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (!TextUtils.isEmpty(display)) {
1122f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                return RecipientEntry.constructGeneratedEntry(display, tokens[0].getAddress(),
1123f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                        isValid);
1124401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira            } else {
1125401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                display = tokens[0].getAddress();
1126401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                if (!TextUtils.isEmpty(display)) {
1127f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    return RecipientEntry.constructFakeEntry(display, isValid);
1128401cd96a036a3752b81698c21d1bd5dce16657e4Mindy Pereira                }
112901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
113001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        }
11314e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        // Unable to validate the token or to create a valid token from it.
11324e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        // Just create a chip the user can edit.
1133dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira        String validatedToken = null;
1134f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        if (mValidator != null && !isValid) {
11354e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            // Try fixing up the entry using the validator.
1136dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira            validatedToken = mValidator.fixText(token).toString();
1137dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira            if (!TextUtils.isEmpty(validatedToken)) {
1138dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                if (validatedToken.contains(token)) {
1139f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // protect against the case of a validator with a null
1140f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // domain,
1141dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // which doesn't add a domain to the token
1142dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
1143dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    if (tokenized.length > 0) {
1144dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                        validatedToken = tokenized[0].getAddress();
1145f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                        isValid = true;
1146dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    }
1147dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                } else {
1148f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // We ran into a case where the token was invalid and
1149f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // removed
1150f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // by the validator. In this case, just use the original
1151f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    // token
1152dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    // and let the user sort out the error chip.
1153dbb8560594f297da570a4dc2c800eefe0faa6c16Mindy Pereira                    validatedToken = null;
1154f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    isValid = false;
1155184d3773dc096c7a6a83f7aeda042fa8346c2024Erik                }
1156ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira            }
11574e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        }
1158ee58f4904fe6d992ad4631604b595ba47d08ca6bMindy Pereira        // Otherwise, fallback to just creating an editable email address chip.
1159f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return RecipientEntry.constructFakeEntry(
1160f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                !TextUtils.isEmpty(validatedToken) ? validatedToken : token, isValid);
116101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
116201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
11634e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira    private boolean isValid(String text) {
11644e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira        return mValidator == null ? true : mValidator.isValid(text);
11654e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira    }
11664e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira
1167f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private static String tokenizeAddress(String destination) {
116801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
116901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        if (tokens != null && tokens.length > 0) {
117001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            return tokens[0].getAddress();
11713bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
117201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        return destination;
11733bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
11743bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
117502a1f5fc67366addfa194b864b85a5d467f0543dMindy Pereira    @Override
1176cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public void setTokenizer(Tokenizer tokenizer) {
1177cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        mTokenizer = tokenizer;
1178cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        super.setTokenizer(mTokenizer);
1179cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1180cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1181a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    @Override
1182a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    public void setValidator(Validator validator) {
1183a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira        mValidator = validator;
1184a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira        super.setValidator(validator);
1185a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    }
1186a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira
1187a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1188a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * We cannot use the default mechanism for replaceText. Instead,
1189a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * we override onItemClickListener so we can get all the associated
1190a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * contact information including display text, address, and id.
1191a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1192cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1193cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    protected void replaceText(CharSequence text) {
1194cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return;
1195cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1196cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1197a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1198a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Dismiss any selected chips when the back key is pressed.
1199a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1200cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
12014c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
120203cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira        if (keyCode == KeyEvent.KEYCODE_BACK && mSelectedChip != null) {
12034c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira            clearSelectedChip();
120403cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira            return true;
12054c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira        }
12064c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira        return super.onKeyPreIme(keyCode, event);
12074c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    }
12084c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira
1209a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1210a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Monitor key presses in this view to see if the user types
1211a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
1212a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the user has entered text that has contact matches and types
1213a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * a commit key, create a chip from the topmost matching contact.
1214a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the user has entered text that has no contact matches and types
1215a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * a commit key, then create a chip from the text they have entered.
1216a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
12174c5fc2c74cb001fa930b1a264fb10a1071317086Mindy Pereira    @Override
1218cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onKeyUp(int keyCode, KeyEvent event) {
1219cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        switch (keyCode) {
12200f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            case KeyEvent.KEYCODE_TAB:
12210f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                if (event.hasNoModifiers()) {
12220f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    if (mSelectedChip != null) {
12230f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        clearSelectedChip();
12240f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    } else {
12250f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                        commitDefault();
12260f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                    }
12270f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira                }
122803cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira                break;
1229cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1230cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return super.onKeyUp(keyCode, event);
1231cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1232cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
12330f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira    private boolean focusNext() {
12340f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        View next = focusSearch(View.FOCUS_DOWN);
12350f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        if (next != null) {
12360f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            next.requestFocus();
12370f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira            return true;
12380f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        }
12390f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira        return false;
12400f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira    }
12410f0df298b8bc7973f98397153802da0bd1607c83Mindy Pereira
1242342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira    /**
1243342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Create a chip from the default selection. If the popup is showing, the
1244342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * default is the first item in the popup suggestions list. Otherwise, it is
1245342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * whatever the user had typed in. End represents where the the tokenizer
1246342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * should search for a token to turn into a chip.
1247342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * @return If a chip was created from a real contact.
1248342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     */
1249a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    private boolean commitDefault() {
125060d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        // If there is no tokenizer, don't try to commit.
125160d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        if (mTokenizer == null) {
125260d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira            return false;
125360d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        }
125412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        Editable editable = getText();
1255342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira        int end = getSelectionEnd();
125612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
12572b4ffc53a3b51631cb6aabf535986a9344ee6cbbMindy Pereira
125879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        if (shouldCreateChip(start, end)) {
125979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int whatEnd = mTokenizer.findTokenEnd(getText(), start);
126079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // In the middle of chip; treat this as an edit
126179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // and commit the whole token.
12624f82d888c680a61b95373740ce68bfb48a242617mindyp            whatEnd = movePastTerminators(whatEnd);
126379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (whatEnd != getSelectionEnd()) {
126479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                handleEdit(start, whatEnd);
126579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                return true;
126612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            }
126779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            return commitChip(start, end , editable);
126879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
126979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        return false;
127079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
127179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
127279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private void commitByCharacter() {
127360d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        // We can't possibly commit by character if we can't tokenize.
127460d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        if (mTokenizer == null) {
127560d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira            return;
127660d96dccf8f82c31976d29842b85f12dc905cfd1Mindy Pereira        }
127779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        Editable editable = getText();
127879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        int end = getSelectionEnd();
127979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        int start = mTokenizer.findTokenStart(editable, end);
128079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        if (shouldCreateChip(start, end)) {
128179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            commitChip(start, end, editable);
128212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
1283ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira        setSelection(getText().length());
128479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
128512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
128679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private boolean commitChip(int start, int end, Editable editable) {
128720c9d620e79ae28994856541761a951074551518Mindy Pereira        ListAdapter adapter = getAdapter();
128820c9d620e79ae28994856541761a951074551518Mindy Pereira        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
1289c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor                && end == getSelectionEnd() && !isPhoneQuery()) {
129079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // choose the first entry.
129179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            submitItemAtPosition(0);
129279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            dismissDropDown();
129379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            return true;
129479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        } else {
129579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
129620c9d620e79ae28994856541761a951074551518Mindy Pereira            if (editable.length() > tokenEnd + 1) {
129720c9d620e79ae28994856541761a951074551518Mindy Pereira                char charAt = editable.charAt(tokenEnd + 1);
129820c9d620e79ae28994856541761a951074551518Mindy Pereira                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
129920c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenEnd++;
130020c9d620e79ae28994856541761a951074551518Mindy Pereira                }
1301d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira            }
130262397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira            String text = editable.toString().substring(start, tokenEnd).trim();
130379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            clearComposingText();
130479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (text != null && text.length() > 0 && !text.equals(" ")) {
130501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                RecipientEntry entry = createTokenizedEntry(text);
1306ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                if (entry != null) {
1307ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
1308ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                    CharSequence chipText = createChip(entry, false);
1309ee6d83fe1da297b2f9af0fb221be376fdc816830Mindy Pereira                    if (chipText != null && start > -1 && end > -1) {
1310f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira                        editable.replace(start, end, chipText);
1311f177bdab5724635aed964de889febe96ecab6bc0Mindy Pereira                    }
1312ba5642a5729d9d1483702d42ae4a21d8cbfaa804Mindy Pereira                }
131320c9d620e79ae28994856541761a951074551518Mindy Pereira                // Only dismiss the dropdown if it is related to the text we
131420c9d620e79ae28994856541761a951074551518Mindy Pereira                // just committed.
131520c9d620e79ae28994856541761a951074551518Mindy Pereira                // For paste, it may not be as there are possibly multiple
131620c9d620e79ae28994856541761a951074551518Mindy Pereira                // tokens being added.
131720c9d620e79ae28994856541761a951074551518Mindy Pereira                if (end == getSelectionEnd()) {
131820c9d620e79ae28994856541761a951074551518Mindy Pereira                    dismissDropDown();
131920c9d620e79ae28994856541761a951074551518Mindy Pereira                }
13205df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                sanitizeBetween();
132112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                return true;
132205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            }
132305dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
132412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
132505dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira    }
132605dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira
132701162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
132801162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    /* package */ void sanitizeBetween() {
13293c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        // Don't sanitize while we are waiting for content to chipify.
13303c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        if (mPendingChipsCount > 0) {
13313c42baf5b81810bce77e776963f20c960865e85bMindy Pereira            return;
13323c42baf5b81810bce77e776963f20c960865e85bMindy Pereira        }
13335df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        // Find the last chip.
1334194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] recips = getSortedRecipients();
13355df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        if (recips != null && recips.length > 0) {
1336194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip last = recips[recips.length - 1];
1337194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip beforeLast = null;
13385df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            if (recips.length > 1) {
13395df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                beforeLast = recips[recips.length - 2];
13405df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
13415df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            int startLooking = 0;
13425df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            int end = getSpannable().getSpanStart(last);
13435df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            if (beforeLast != null) {
13445df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                startLooking = getSpannable().getSpanEnd(beforeLast);
1345399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                Editable text = getText();
134601162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira                if (startLooking == -1 || startLooking > text.length() - 1) {
1347399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                    // There is nothing after this chip.
1348399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                    return;
1349399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                }
1350399bda87ad1a4d003609d6d27afc50c8359846b9Mindy Pereira                if (text.charAt(startLooking) == ' ') {
13515df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                    startLooking++;
13525df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                }
13535df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
1354e13775cc6e9342e42db0b853cc42dbfda6d1365fMindy Pereira            if (startLooking >= 0 && end >= 0 && startLooking < end) {
13555df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira                getText().delete(startLooking, end);
13565df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira            }
13575df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        }
13585df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira    }
13595df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira
136079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private boolean shouldCreateChip(int start, int end) {
1361f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
136232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira    }
136332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira
136432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira    private boolean alreadyHasChip(int start, int end) {
1365f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (mNoChips) {
1366f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            return true;
1367f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1368194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips =
1369194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                getSpannable().getSpans(start, end, DrawableRecipientChip.class);
137032366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        if ((chips == null || chips.length == 0)) {
137132366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            return false;
137264af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira        }
137332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira        return true;
137479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
137579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
137679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private void handleEdit(int start, int end) {
1377a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira        if (start == -1 || end == -1) {
1378a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            // This chip no longer exists in the field.
1379a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            dismissDropDown();
1380a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira            return;
1381a49e7fa477692ba2f6208a1a3e80e75e67e65a60Mindy Pereira        }
138279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        // This is in the middle of a chip, so select out the whole chip
138379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        // and commit it.
138479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        Editable editable = getText();
138579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        setSelection(end);
138679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        String text = getText().toString().substring(start, end);
1387fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira        if (!TextUtils.isEmpty(text)) {
1388f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            RecipientEntry entry = RecipientEntry.constructFakeEntry(text, isValid(text));
1389fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
1390fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira            CharSequence chipText = createChip(entry, false);
13915cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira            int selEnd = getSelectionEnd();
13925cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira            if (chipText != null && start > -1 && selEnd > -1) {
13935cfd6fea275724ce223cb8f4a1821922c8763631Mindy Pereira                editable.replace(start, selEnd, chipText);
13942d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            }
1395fe52b97e748ec0b9bd44b759780ed42b9dcee7ffMindy Pereira        }
1396ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira        dismissDropDown();
139764af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira    }
139864af2da9970f59eee2dfd0c8dd2a06f09171bad2Mindy Pereira
1399a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1400a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If there is a selected chip, delegate the key events
1401a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * to the selected chip.
1402a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1403cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1404cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onKeyDown(int keyCode, KeyEvent event) {
140595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
140695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
140795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                mAlternatesPopup.dismiss();
140895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
140995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            removeChip(mSelectedChip);
1410cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1411cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
14122fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy        switch (keyCode) {
14132fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy            case KeyEvent.KEYCODE_ENTER:
14142fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy            case KeyEvent.KEYCODE_DPAD_CENTER:
14152fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                if (event.hasNoModifiers()) {
14162fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    if (commitDefault()) {
14172fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        return true;
14182fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    }
14192fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    if (mSelectedChip != null) {
14202fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        clearSelectedChip();
14212fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        return true;
14222fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    } else if (focusNext()) {
14232fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                        return true;
14242fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                    }
14252fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                }
14262fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy                break;
14272fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy        }
14282fe0cffa8dce1712fb5b568ab06e13857ef4f7c1Scott Kennedy
1429cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return super.onKeyDown(keyCode, event);
14309159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira    }
14319159cd236a0bd16c3ee632eb085c4e432909493cMindy Pereira
143201162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
143301162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    /* package */ Spannable getSpannable() {
1434e33555f13a9b05d835cb860e2c30ef40af3c8502Erik        return getText();
1435cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1436cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1437194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private int getChipStart(DrawableRecipientChip chip) {
143895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return getSpannable().getSpanStart(chip);
143995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
144095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1441194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private int getChipEnd(DrawableRecipientChip chip) {
144295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        return getSpannable().getSpanEnd(chip);
144395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
144495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1445cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /**
1446cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * Instead of filtering on the entire contents of the edit box,
1447cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * this subclass method filters on the range from
1448cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
1449cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * if the length of that range meets or exceeds {@link #getThreshold}
1450cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     * and makes sure that the range is not already a Chip.
1451cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     */
1452cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1453cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    protected void performFiltering(CharSequence text, int keyCode) {
1454093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        boolean isCompletedToken = isCompletedToken(text);
1455093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        if (enoughToFilter() && !isCompletedToken) {
1456cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            int end = getSelectionEnd();
1457cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            int start = mTokenizer.findTokenStart(text, end);
1458cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // If this is a RecipientChip, don't filter
1459cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // on its contents.
1460cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            Spannable span = getSpannable();
1461194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip[] chips = span.getSpans(start, end, DrawableRecipientChip.class);
1462cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (chips != null && chips.length > 0) {
146341e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin                dismissDropDown();
1464cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                return;
1465cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1466093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp        } else if (isCompletedToken) {
146741e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin            dismissDropDown();
1468093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            return;
1469cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1470cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        super.performFiltering(text, keyCode);
1471cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1472cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
147320c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
147420c9d620e79ae28994856541761a951074551518Mindy Pereira    /*package*/ boolean isCompletedToken(CharSequence text) {
147520c9d620e79ae28994856541761a951074551518Mindy Pereira        if (TextUtils.isEmpty(text)) {
147620c9d620e79ae28994856541761a951074551518Mindy Pereira            return false;
147720c9d620e79ae28994856541761a951074551518Mindy Pereira        }
147820c9d620e79ae28994856541761a951074551518Mindy Pereira        // Check to see if this is a completed token before filtering.
147920c9d620e79ae28994856541761a951074551518Mindy Pereira        int end = text.length();
148020c9d620e79ae28994856541761a951074551518Mindy Pereira        int start = mTokenizer.findTokenStart(text, end);
148120c9d620e79ae28994856541761a951074551518Mindy Pereira        String token = text.toString().substring(start, end).trim();
148220c9d620e79ae28994856541761a951074551518Mindy Pereira        if (!TextUtils.isEmpty(token)) {
148320c9d620e79ae28994856541761a951074551518Mindy Pereira            char atEnd = token.charAt(token.length() - 1);
148420c9d620e79ae28994856541761a951074551518Mindy Pereira            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
148520c9d620e79ae28994856541761a951074551518Mindy Pereira        }
148620c9d620e79ae28994856541761a951074551518Mindy Pereira        return false;
148720c9d620e79ae28994856541761a951074551518Mindy Pereira    }
148820c9d620e79ae28994856541761a951074551518Mindy Pereira
1489cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private void clearSelectedChip() {
1490cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        if (mSelectedChip != null) {
149195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            unselectChip(mSelectedChip);
1492cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            mSelectedChip = null;
1493cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1494b86dcd5230ebcc57e5fc7a669c2304aca142dbf5Mindy Pereira        setCursorVisible(true);
1495cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1496cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1497a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1498a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Monitor touch events in the RecipientEditTextView.
1499a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If the view does not have focus, any tap on the view
1500a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * will just focus the view. If the view has focus, determine
1501a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * if the touch target is a recipient chip. If it is and the chip
1502a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * is not selected, select it and clear any other selected chips.
1503a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * If it isn't, then select that chip.
1504a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1505cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1506cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public boolean onTouchEvent(MotionEvent event) {
150705dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        if (!isFocused()) {
150805dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            // Ignore any chip taps until this view is focused.
150905dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira            return super.onTouchEvent(event);
151005dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        }
1511cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        boolean handled = super.onTouchEvent(event);
151205dbd33931e590ccb538bbf9d7d93d53aaee366eMindy Pereira        int action = event.getAction();
1513cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        boolean chipWasSelected = false;
15142e905906f83fb1285498f09fce4db4a5878efbccMindy Pereira        if (mSelectedChip == null) {
15151d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira            mGestureDetector.onTouchEvent(event);
15161d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
1517bfedc1e199e57dcda494389fdca0750e1f165135Mindy Pereira        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
1518cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            float x = event.getX();
1519cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            float y = event.getY();
15201650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy            int offset = putOffsetInRange(x, y);
1521194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip currentChip = findChip(offset);
1522cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (currentChip != null) {
1523cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                if (action == MotionEvent.ACTION_UP) {
1524cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    if (mSelectedChip != null && mSelectedChip != currentChip) {
1525cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                        clearSelectedChip();
152695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1527cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    } else if (mSelectedChip == null) {
1528a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira                        setSelection(getText().length());
1529a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira                        commitDefault();
153095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        mSelectedChip = selectChip(currentChip);
1531cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    } else {
153295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                        onClick(mSelectedChip, offset, x, y);
1533cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                    }
1534cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                }
1535cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                chipWasSelected = true;
1536c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira                handled = true;
15375e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            } else if (mSelectedChip != null && shouldShowEditableText(mSelectedChip)) {
1538c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira                chipWasSelected = true;
1539cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1540cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1541cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
1542cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            clearSelectedChip();
1543cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1544cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return handled;
1545cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1546cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1547c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    private void scrollLineIntoView(int line) {
1548c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        if (mScrollView != null) {
1549093ecdd3cde3e54df3fb30f5ca47067fdcf46f3cmindyp            mScrollView.smoothScrollBy(0, calculateOffsetFromBottom(line));
1550c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira        }
1551c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira    }
1552c1fed8b74327fccbab18458aef2d1bb0a6996e9cMindy Pereira
1553272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy    private void showAlternates(final DrawableRecipientChip currentChip,
1554272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            final ListPopupWindow alternatesPopup, final int width) {
1555272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy        new AsyncTask<Void, Void, ListAdapter>() {
1556272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            @Override
1557272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            protected ListAdapter doInBackground(final Void... params) {
1558272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                return createAlternatesAdapter(currentChip);
1559272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            }
1560272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy
15616b7110f320c978c368c28bdb06212c6a6df12f1fAlice Yang            @Override
1562272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            protected void onPostExecute(final ListAdapter result) {
1563fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler                if (!mAttachedToWindow) {
1564fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler                    return;
1565fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler                }
1566272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                int line = getLayout().getLineForOffset(getChipStart(currentChip));
1567272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                int bottom;
1568272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                if (line == getLineCount() -1) {
1569272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    bottom = 0;
1570272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                } else {
1571272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    bottom = -(int) ((mChipHeight + (2 * mLineSpacingExtra)) * (Math
1572272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                            .abs(getLineCount() - 1 - line)));
1573272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                }
1574272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // Align the alternates popup with the left side of the View,
1575272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // regardless of the position of the chip tapped.
1576272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setWidth(width);
1577272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setAnchorView(RecipientEditTextView.this);
1578272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setVerticalOffset(bottom);
1579272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setAdapter(result);
1580272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.setOnItemClickListener(mAlternatesListener);
1581272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // Clear the checked item.
1582272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                mCheckedItem = -1;
1583272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                alternatesPopup.show();
1584272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                ListView listView = alternatesPopup.getListView();
1585272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1586272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // Checked item would be -1 if the adapter has not
1587272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // loaded the view that should be checked yet. The
1588272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // variable will be set correctly when onCheckedItemChanged
1589272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                // is called in a separate thread.
1590272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                if (mCheckedItem != -1) {
1591272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    listView.setItemChecked(mCheckedItem, true);
1592272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                    mCheckedItem = -1;
1593272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy                }
1594272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy            }
1595272aae0dadf8b63b83e61bc7d9f5093f51e06564Scott Kennedy        }.execute((Void[]) null);
159695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
159795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1598194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
15997a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(),
16007a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy                chip.getDirectoryId(), chip.getLookupKey(), chip.getDataId(),
1601b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                getAdapter().getQueryType(), this, mDropdownChipLayouter);
160295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
160395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
1604194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) {
1605b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(),
1606b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin                mDropdownChipLayouter);
160701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
160801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
1609aa2afffe7aba707c2406f2e4503fa6037c4cd196Andy Stadler    @Override
161095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    public void onCheckedItemChanged(int position) {
161195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        ListView listView = mAlternatesPopup.getListView();
161295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (listView != null && listView.getCheckedItemCount() == 0) {
161395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            listView.setItemChecked(position, true);
161495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        }
161598b547f2eeb80259036e3f528636d7cbd823bf6dMindy Pereira        mCheckedItem = position;
161695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
161795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira
16181650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int putOffsetInRange(final float x, final float y) {
16191650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int offset;
16201650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
16211650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
16221650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy            offset = getOffsetForPosition(x, y);
16231650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        } else {
16241650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy            offset = supportGetOffsetForPosition(x, y);
16251650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        }
16261650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
16271650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return putOffsetInRange(offset);
16281650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
16291650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
1630cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // TODO: This algorithm will need a lot of tweaking after more people have used
1631cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
1632cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    // what comes before the finger.
1633cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private int putOffsetInRange(int o) {
1634cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int offset = o;
1635cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Editable text = getText();
1636cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int length = text.length();
1637cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Remove whitespace from end to find "real end"
1638cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int realLength = length;
1639cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        for (int i = length - 1; i >= 0; i--) {
1640cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            if (text.charAt(i) == ' ') {
1641cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                realLength--;
1642cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            } else {
1643cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                break;
1644cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1645cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1646cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1647fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // If the offset is beyond or at the end of the text,
1648fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        // leave it alone.
1649fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        if (offset >= realLength) {
1650cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            return offset;
1651cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1652fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        Editable editable = getText();
1653b88ee450829eb4ac24fb47c377b9ec3aab0782daMindy Pereira        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
1654cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            // Keep walking backward!
1655cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            offset--;
1656cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1657cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return offset;
1658cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1659cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1660f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy    private static int findText(Editable text, int offset) {
1661fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        if (text.charAt(offset) != ' ') {
1662fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira            return offset;
1663fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        }
1664fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira        return -1;
1665fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira    }
1666fd68ea64d80f3b299b20ab72b6ed71d3c4ff6c9cMindy Pereira
1667194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private DrawableRecipientChip findChip(int offset) {
1668194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips =
1669194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                getSpannable().getSpans(0, getText().length(), DrawableRecipientChip.class);
1670cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Find the chip that contains this offset.
1671cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        for (int i = 0; i < chips.length; i++) {
1672194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip = chips[i];
167395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int start = getChipStart(chip);
167495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int end = getChipEnd(chip);
167595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (offset >= start && offset <= end) {
1676cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira                return chip;
1677cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
1678cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1679cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return null;
1680cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1681cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
168201162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
1683aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Use this method to generate text to add to the list of addresses.
168432aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira    /* package */String createAddressText(RecipientEntry entry) {
16853ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        String display = entry.getDisplayName();
16863ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        String address = entry.getDestination();
16873ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
16883ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira            display = null;
16893ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        }
1690abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        String trimmedDisplayText;
1691c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor        if (isPhoneQuery() && isPhoneNumber(address)) {
1692abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            trimmedDisplayText = address.trim();
1693abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor        } else {
1694abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            if (address != null) {
1695abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                // Tokenize out the address in case the address already
1696abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                // contained the username as well.
1697abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
1698abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                if (tokenized != null && tokenized.length > 0) {
1699abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                    address = tokenized[0].getAddress();
1700abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor                }
1701ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira            }
1702abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            Rfc822Token token = new Rfc822Token(display, address, null);
1703abff606ceb5e103d4a21c56fb9b78d7844c8e5b7Tom Taylor            trimmedDisplayText = token.toString().trim();
1704d8c15c328eaa109946f8f9093f3c2f2773d525ddMindy Pereira        }
17053ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira        int index = trimmedDisplayText.indexOf(",");
170632aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira        return mTokenizer != null && !TextUtils.isEmpty(trimmedDisplayText)
170732aff5f5bdc7c860169e50eacb7120983b3901a8Mindy Pereira                && index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
1708aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
1709aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    }
1710aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
1711aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
1712aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Use this method to generate text to display in a chip.
1713aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /*package*/ String createChipDisplayText(RecipientEntry entry) {
1714aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String display = entry.getDisplayName();
1715aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String address = entry.getDestination();
1716aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
1717aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            display = null;
1718aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
1719aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (!TextUtils.isEmpty(display)) {
1720aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return display;
1721aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        } else if (!TextUtils.isEmpty(address)){
1722aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return address;
1723aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        } else {
1724aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            return new Rfc822Token(display, address, null).toString();
1725aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
17263ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira    }
17273ea9955a3c06d241bc6e4ab659ec3a1d069fd6dfMindy Pereira
17280ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
1729aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        String displayText = createAddressText(entry);
17302d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        if (TextUtils.isEmpty(displayText)) {
17312d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            return null;
17322d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        }
1733f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        SpannableString chipText = null;
1734cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        // Always leave a blank space at the end of a chip.
1735f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy        int textLength = displayText.length() - 1;
1736f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        chipText = new SpannableString(displayText);
1737f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (!mNoChips) {
1738f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            try {
1739194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip chip = constructChipSpan(entry, pressed,
17405e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                        false /* leave space for contact icon */);
1741f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                chipText.setSpan(chip, 0, textLength,
1742f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1743f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                chip.setOriginalText(chipText.toString());
1744f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            } catch (NullPointerException e) {
1745f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                Log.e(TAG, e.getMessage(), e);
1746f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                return null;
1747f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            }
1748cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1749cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return chipText;
1750cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1751cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1752a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1753a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * When an item in the suggestions list has been clicked, create a chip from the
1754a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * contact information of the selected item.
1755a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1756cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    @Override
1757cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1758114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor        if (position < 0) {
1759114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor            return;
1760114c89364bd00a445f0b017ae658928c1dc26c5aTom Taylor        }
1761cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        submitItemAtPosition(position);
1762cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1763cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1764cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    private void submitItemAtPosition(int position) {
1765858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy        RecipientEntry entry = createValidatedEntry(getAdapter().getItem(position));
1766f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik        if (entry == null) {
1767f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik            return;
1768f7eaa5fc06e3dd57457f7bf0169374a4c9a1e413Erik        }
1769cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        clearComposingText();
1770cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1771cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int end = getSelectionEnd();
1772cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        int start = mTokenizer.findTokenStart(getText(), end);
1773cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1774cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        Editable editable = getText();
1775cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        QwertyKeyListener.markAsReplaced(editable, start, end, "");
1776e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira        CharSequence chip = createChip(entry, false);
1777005b2e29742d7b9f3ecc505a50b11d91bd44c818Mindy Pereira        if (chip != null && start >= 0 && end >= 0) {
1778e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira            editable.replace(start, end, chip);
1779e90bfad1a7cc118e721b723aba48e71b952e5c6eMindy Pereira        }
17805df0aa8368be2733caee5d57f5b20357b0f5d99dMindy Pereira        sanitizeBetween();
1781cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1782cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
17833bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    private RecipientEntry createValidatedEntry(RecipientEntry item) {
17843bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        if (item == null) {
17853bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            return null;
17863bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
17873bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        final RecipientEntry entry;
17883bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // If the display name and the address are the same, or if this is a
17893bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // valid contact, but the destination is invalid, then make this a fake
17903bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        // recipient that is editable.
17913bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        String destination = item.getDestination();
17925e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (!isPhoneQuery() && item.getContactId() == RecipientEntry.GENERATED_CONTACT) {
17935e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            entry = RecipientEntry.constructGeneratedEntry(item.getDisplayName(),
1794f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    destination, item.isValid());
17955e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        } else if (RecipientEntry.isCreatedRecipient(item.getContactId())
1796b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                && (TextUtils.isEmpty(item.getDisplayName())
1797b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                        || TextUtils.equals(item.getDisplayName(), destination)
1798b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                        || (mValidator != null && !mValidator.isValid(destination)))) {
1799f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            entry = RecipientEntry.constructFakeEntry(destination, item.isValid());
18003bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        } else {
18013bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            entry = item;
18023bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        }
18033bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        return entry;
18043bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira    }
18053bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira
1806cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /** Returns a collection of contact Id for each chip inside this View. */
1807cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /* package */ Collection<Long> getContactIds() {
1808cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        final Set<Long> result = new HashSet<Long>();
1809194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] chips = getSortedRecipients();
1810df4457285cf0a54d957f1fad3bbc07112f750818Mindy Pereira        if (chips != null) {
1811194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            for (DrawableRecipientChip chip : chips) {
1812df4457285cf0a54d957f1fad3bbc07112f750818Mindy Pereira                result.add(chip.getContactId());
1813df4457285cf0a54d957f1fad3bbc07112f750818Mindy Pereira            }
1814cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
1815cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        return result;
1816cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    }
1817cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
1818aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira
1819aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /** Returns a collection of data Id for each chip inside this View. May be null. */
1820aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    /* package */ Collection<Long> getDataIds() {
1821aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        final Set<Long> result = new HashSet<Long>();
1822194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip [] chips = getSortedRecipients();
1823aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        if (chips != null) {
1824194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            for (DrawableRecipientChip chip : chips) {
1825aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira                result.add(chip.getDataId());
1826aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira            }
1827aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        }
1828aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira        return result;
1829d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira    }
1830d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira
183101162ce6739af1c9d9870f8e7e489f805c7e6794Mindy Pereira    // Visible for testing.
1832194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /* package */DrawableRecipientChip[] getSortedRecipients() {
1833194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] recips = getSpannable()
1834194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                .getSpans(0, getText().length(), DrawableRecipientChip.class);
1835194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        ArrayList<DrawableRecipientChip> recipientsList = new ArrayList<DrawableRecipientChip>(
1836194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                Arrays.asList(recips));
1837e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        final Spannable spannable = getSpannable();
1838194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        Collections.sort(recipientsList, new Comparator<DrawableRecipientChip>() {
1839e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
1840e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            @Override
1841194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            public int compare(DrawableRecipientChip first, DrawableRecipientChip second) {
1842e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int firstStart = spannable.getSpanStart(first);
1843e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int secondStart = spannable.getSpanStart(second);
1844e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                if (firstStart < secondStart) {
1845e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return -1;
1846e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                } else if (firstStart > secondStart) {
1847e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return 1;
1848e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                } else {
1849e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    return 0;
1850e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                }
1851e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            }
1852e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        });
1853194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        return recipientsList.toArray(new DrawableRecipientChip[recipientsList.size()]);
1854e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    }
1855e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
185612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
185712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
185812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
185912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
186012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
186112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
186212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public void onDestroyActionMode(ActionMode mode) {
186312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
186412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
186512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
186612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
186712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
186812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
186912cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1870a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1871a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * No chips are selectable.
1872a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
187312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    @Override
187412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
187512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        return false;
187612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
187712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
1878d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
1879d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ImageSpan getMoreChip() {
1880dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
1881dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira                MoreImageSpan.class);
1882dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
1883dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira    }
1884dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira
1885f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    private MoreImageSpan createMoreSpan(int count) {
1886f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        String moreText = String.format(mMoreItem.getText().toString(), count);
1887f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        TextPaint morePaint = new TextPaint(getPaint());
1888f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        morePaint.setTextSize(mMoreItem.getTextSize());
1889f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        morePaint.setColor(mMoreItem.getCurrentTextColor());
1890f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
1891f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                + mMoreItem.getPaddingRight();
1892f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int height = getLineHeight();
1893f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1894f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Canvas canvas = new Canvas(drawable);
1895f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int adjustedHeight = height;
1896f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Layout layout = getLayout();
1897f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (layout != null) {
1898f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            adjustedHeight -= layout.getLineDescent(0);
1899f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1900f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
1901f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1902f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Drawable result = new BitmapDrawable(getResources(), drawable);
1903f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        result.setBounds(0, 0, width, height);
1904f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return new MoreImageSpan(result);
1905f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1906f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1907f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1908f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /*package*/ void createMoreChipPlainText() {
1909f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        // Take the first <= CHIP_LIMIT addresses and get to the end of the second one.
1910f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        Editable text = getText();
1911f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int start = 0;
1912f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int end = start;
1913f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        for (int i = 0; i < CHIP_LIMIT; i++) {
1914f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            end = movePastTerminators(mTokenizer.findTokenEnd(text, start));
1915f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            start = end; // move to the next token and get its end.
1916f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1917f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        // Now, count total addresses.
1918f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        start = 0;
1919f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int tokenCount = countTokens(text);
1920f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT);
1921f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(end, text.length()));
1922f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1923f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        text.replace(end, text.length(), chipText);
1924f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        mMoreChip = moreSpan;
1925f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1926f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1927f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    // Visible for testing.
1928f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    /* package */int countTokens(Editable text) {
1929f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int tokenCount = 0;
1930f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        int start = 0;
1931f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        while (start < text.length()) {
1932f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            start = movePastTerminators(mTokenizer.findTokenEnd(text, start));
1933f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            tokenCount++;
1934f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            if (start >= text.length()) {
1935f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                break;
1936f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            }
1937f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1938f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        return tokenCount;
1939f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira    }
1940f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1941a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
1942342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * Create the more chip. The more chip is text that replaces any chips that
1943342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * do not fit in the pre-defined available space when the
1944342142087308bd9f5f7d682bf2ccd2420a3c7538Mindy Pereira     * RecipientEditTextView loses focus.
1945a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
1946d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
1947d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /* package */ void createMoreChip() {
1948f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (mNoChips) {
1949f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            createMoreChipPlainText();
1950f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            return;
1951f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
1952f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1953076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        if (!mShouldShrink) {
1954076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira            return;
1955076d1f93a2613611f9f3aca8c8615ffea9b3e11cMindy Pereira        }
1956e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
1957e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        if (tempMore.length > 0) {
1958e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            getSpannable().removeSpan(tempMore[0]);
1959e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        }
1960194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip[] recipients = getSortedRecipients();
1961f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira
1962d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira        if (recipients == null || recipients.length <= CHIP_LIMIT) {
19633bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            mMoreChip = null;
19643bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira            return;
196512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
1966e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        Spannable spannable = getSpannable();
1967d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira        int numRecipients = recipients.length;
196812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int overage = numRecipients - CHIP_LIMIT;
1969f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        MoreImageSpan moreSpan = createMoreSpan(overage);
1970194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        mRemovedSpans = new ArrayList<DrawableRecipientChip>();
197112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int totalReplaceStart = 0;
197212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        int totalReplaceEnd = 0;
1973e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        Editable text = getText();
197421cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        for (int i = numRecipients - overage; i < recipients.length; i++) {
197521cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            mRemovedSpans.add(recipients[i]);
1976364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            if (i == numRecipients - overage) {
197721cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                totalReplaceStart = spannable.getSpanStart(recipients[i]);
1978364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            }
197921cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            if (i == recipients.length - 1) {
198021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
1981364e7cf182a4124808d3a699dbea8dd1cc1d7bedMindy Pereira            }
1982e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
1983e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int spanStart = spannable.getSpanStart(recipients[i]);
1984e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                int spanEnd = spannable.getSpanEnd(recipients[i]);
1985e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
19861852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
198721cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            spannable.removeSpan(recipients[i]);
198812cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
1989f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        if (totalReplaceEnd < text.length()) {
1990f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            totalReplaceEnd = text.length();
1991f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira        }
19921852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        int end = Math.max(totalReplaceStart, totalReplaceEnd);
19931852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        int start = Math.min(totalReplaceStart, totalReplaceEnd);
19941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        SpannableString chipText = new SpannableString(text.subSequence(start, end));
199512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
19961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        text.replace(start, end, chipText);
19973bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira        mMoreChip = moreSpan;
19985e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        // If adding the +more chip goes over the limit, resize accordingly.
19995e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (!isPhoneQuery() && getLineCount() > mMaxLines) {
20005e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            setMaxLines(getLineCount());
20015e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        }
200212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
200312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
2004a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira    /**
2005a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * Replace the more chip, if it exists, with all of the recipient chips it had
2006a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     * replaced when the RecipientEditTextView gains focus.
2007a816690a8b3ecfeb537f6ad403a903f39205f738Mindy Pereira     */
2008d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    // Visible for testing.
2009d71925073f008dccd8c4b65d5d66534e6451e64dMindy Pereira    /*package*/ void removeMoreChip() {
201012cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        if (mMoreChip != null) {
201112cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            Spannable span = getSpannable();
201212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            span.removeSpan(mMoreChip);
201312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            mMoreChip = null;
201412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            // Re-add the spans that were removed.
201512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
201612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                // Recreate each removed span.
2017194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] recipients = getSortedRecipients();
201854effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                // Start the search for tokens after the last currently visible
201954effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                // chip.
2020ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                if (recipients == null || recipients.length == 0) {
2021ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                    return;
2022ee48f7311ec169af7ed134cf5c9c5e16b243cf05Mindy Pereira                }
202354effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                int end = span.getSpanEnd(recipients[recipients.length - 1]);
202412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                Editable editable = getText();
2025194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                for (DrawableRecipientChip chip : mRemovedSpans) {
2026e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    int chipStart;
20273bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    int chipEnd;
20283bb52162b7e842243d4a14e73c15b20dbd1804d7Mindy Pereira                    String token;
2029e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    // Need to find the location of the chip, again.
2030e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira                    token = (String) chip.getOriginalText();
203154effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // As we find the matching recipient for the remove spans,
203254effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // reduce the size of the string we need to search.
203354effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // That way, if there are duplicates, we always find the correct
203454effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    // recipient.
203554effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    chipStart = editable.toString().indexOf(token, end);
203654effe95bd2f988e5d7472f17f4fafdc29fe616eMindy Pereira                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
20370d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    // Only set the span if we found a matching token.
20380d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    if (chipStart != -1) {
20390d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                        editable.setSpan(chip, chipStart, chipEnd,
20400d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
20410d8b77a804fa34cd6fadc632067d562a69b7026aMindy Pereira                    }
204212cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                }
204312cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira                mRemovedSpans.clear();
204412cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira            }
204512cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira        }
204612cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira    }
204712cf3fc6e24ffeb3b84ec5fbbaa7add81b25db09Mindy Pereira
2048cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira    /**
204995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Show specified chip as selected. If the RecipientChip is just an email address,
205095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * selecting the chip will take the contents of the chip and place it at
205195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * the end of the RecipientEditTextView for inline editing. If the
205295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * RecipientChip is a complete contact, then selecting the chip
205395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * will change the background color of the chip, show the delete icon,
205495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * and a popup window with the address in use highlighted and any other
205595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * alternate addresses for the contact.
205695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @param currentChip Chip to select.
205795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @return A RecipientChip in the selected state or null if the chip
205895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * just contained an email address.
2059cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira     */
2060194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private DrawableRecipientChip selectChip(DrawableRecipientChip currentChip) {
20615e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        if (shouldShowEditableText(currentChip)) {
206201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            CharSequence text = currentChip.getValue();
206301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            Editable editable = getText();
2064f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            Spannable spannable = getSpannable();
2065f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            int spanStart = spannable.getSpanStart(currentChip);
2066f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            int spanEnd = spannable.getSpanEnd(currentChip);
2067f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            spannable.removeSpan(currentChip);
2068f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            editable.delete(spanStart, spanEnd);
206901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            setCursorVisible(true);
207001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            setSelection(editable.length());
2071f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            editable.append(text);
2072f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            return constructChipSpan(
2073f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    RecipientEntry.constructFakeEntry((String) text, isValid(text.toString())),
2074f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                    true, false);
20757a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
207695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int start = getChipStart(currentChip);
207795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            int end = getChipEnd(currentChip);
207895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            getSpannable().removeSpan(currentChip);
2079194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip newChip;
208095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            try {
2081f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (mNoChips) {
2082f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    return null;
2083f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
2084f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                newChip = constructChipSpan(currentChip.getEntry(), true, false);
208595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            } catch (NullPointerException e) {
208695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Log.e(TAG, e.getMessage(), e);
208795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                return null;
208895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            }
20890ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira            Editable editable = getText();
209095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
2091d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            if (start == -1 || end == -1) {
209295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
2093d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            } else {
209404da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
2095d4c4e77389def134f5751853b838e9d5edb80be4Mindy Pereira            }
209695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            newChip.setSelected(true);
20975e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            if (shouldShowEditableText(newChip)) {
209801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
209981fd3d1ed9ea08706e297a227fcab10eac2cf0e3Mindy Pereira            }
2100f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy            showAddress(newChip, mAddressPopup, getWidth());
21014e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            setCursorVisible(false);
210295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            return newChip;
210395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        } else {
210401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            int start = getChipStart(currentChip);
210501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            int end = getChipEnd(currentChip);
210601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            getSpannable().removeSpan(currentChip);
2107194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip newChip;
210801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            try {
2109f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                newChip = constructChipSpan(currentChip.getEntry(), true, false);
211001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            } catch (NullPointerException e) {
211101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                Log.e(TAG, e.getMessage(), e);
211201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                return null;
211301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
211495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            Editable editable = getText();
211501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
211601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            if (start == -1 || end == -1) {
211701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                Log.d(TAG, "The chip being selected no longer exists but should.");
211801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            } else {
211901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
212001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
212101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            newChip.setSelected(true);
21225e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp            if (shouldShowEditableText(newChip)) {
212301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
212401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
2125f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy            showAlternates(newChip, mAlternatesPopup, getWidth());
21264e18d8b62dc38558c8911470ef830b638a26cf00Mindy Pereira            setCursorVisible(false);
212701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            return newChip;
21280ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira        }
212995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
21300ab7e735e82c81baf9ab87d028611561ce0592b7Mindy Pereira
2131194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private boolean shouldShowEditableText(DrawableRecipientChip currentChip) {
21325e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        long contactId = currentChip.getContactId();
21335e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp        return contactId == RecipientEntry.INVALID_CONTACT
21345e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT);
21355e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp    }
2136cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
2137194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private void showAddress(final DrawableRecipientChip currentChip, final ListPopupWindow popup,
2138f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy            int width) {
2139fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        if (!mAttachedToWindow) {
2140fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler            return;
2141fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        }
214201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        int line = getLayout().getLineForOffset(getChipStart(currentChip));
214301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        int bottom = calculateOffsetFromBottom(line);
214401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        // Align the alternates popup with the left side of the View,
214501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        // regardless of the position of the chip tapped.
214601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setWidth(width);
214701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setAnchorView(this);
214801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setVerticalOffset(bottom);
214901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setAdapter(createSingleAddressAdapter(currentChip));
215001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.setOnItemClickListener(new OnItemClickListener() {
215101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            @Override
215201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
215301382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                unselectChip(currentChip);
215401382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira                popup.dismiss();
215501382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            }
215601382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        });
215701382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        popup.show();
215801382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        ListView listView = popup.getListView();
215901382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
216001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira        listView.setItemChecked(0, true);
216101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira    }
216201382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira
216395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
216495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Remove selection from this chip. Unselecting a RecipientChip will render
2165f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira     * the chip without a delete icon and with an unfocused background. This is
2166f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira     * called when the RecipientChip no longer has focus.
216795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2168194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private void unselectChip(DrawableRecipientChip chip) {
216995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int start = getChipStart(chip);
217095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int end = getChipEnd(chip);
217195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable editable = getText();
2172e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira        mSelectedChip = null;
217395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (start == -1 || end == -1) {
2174f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira            Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
2175c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            setSelection(editable.length());
2176c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            commitDefault();
217795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        } else {
2178e82e61f026fe8edfc12743fb038ddac9af5cf1efMindy Pereira            getSpannable().removeSpan(chip);
217995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            QwertyKeyListener.markAsReplaced(editable, start, end, "");
218004da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            editable.removeSpan(chip);
218104da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            try {
2182f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (!mNoChips) {
2183f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                    editable.setSpan(constructChipSpan(chip.getEntry(), false, false),
21845e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                            start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
2185f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
218604da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            } catch (NullPointerException e) {
218704da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira                Log.e(TAG, e.getMessage(), e);
218804da325c2112c4ef192276f1d7701cbef6635d1bMindy Pereira            }
2189cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
219095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setCursorVisible(true);
219195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setSelection(editable.length());
219295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
219395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mAlternatesPopup.dismiss();
2194cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
219595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2196cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
219795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
219895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Return whether a touch event was inside the delete target of
219995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * a selected chip. It is in the delete target if:
220095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * 1) the x and y points of the event are within the
220195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * delete assset.
220295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * 2) the point tapped would have caused a cursor to appear
220395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * right after the selected chip.
220495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * @return boolean
220595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2206194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private boolean isInDelete(DrawableRecipientChip chip, int offset, float x, float y) {
220795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Figure out the bounds of this chip and whether or not
220895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // the user clicked in the X portion.
2209f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy        // TODO: Should x and y be used, or removed?
2210b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        if (mDisableDelete) {
2211b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin            return false;
2212b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        }
2213b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin
2214b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin        return chip.isSelected() &&
2215c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                ((mAvatarPosition == AVATAR_POSITION_END && offset == getChipEnd(chip)) ||
2216c0e6023ca7129914a3850bceb1a151acc75fd1f8Andrew Sapperstein                (mAvatarPosition != AVATAR_POSITION_END && offset == getChipStart(chip)));
221795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
22185086391a478c3b1badbb86074c3cef72126c7d0fMindy Pereira
221995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
222095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Remove the chip and any text associated with it from the RecipientEditTextView.
222195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
222297cb25912dab282cf732757f68b0405ed005f00bMindy Pereira    // Visible for testing.
2223b10d1c652d0416c284d9792fc9a0a92b3acd51caKevin Lin    /* package */void removeChip(DrawableRecipientChip chip) {
222495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Spannable spannable = getSpannable();
222595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int spanStart = spannable.getSpanStart(chip);
222695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int spanEnd = spannable.getSpanEnd(chip);
222795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable text = getText();
222862397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        int toDelete = spanEnd;
222995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
223095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        // Clear that there is a selected chip before updating any text.
223195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
223295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mSelectedChip = null;
2233cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
223462397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        // Always remove trailing spaces when removing a chip.
223562397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
223662397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira            toDelete++;
223762397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira        }
223895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        spannable.removeSpan(chip);
2239f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira        if (spanStart >= 0 && toDelete > 0) {
2240f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira            text.delete(spanStart, toDelete);
2241f6d4bfce7cae3cc385396963dc8ea3eda90b917eMindy Pereira        }
224295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
224395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            clearSelectedChip();
2244cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
224595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2246cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
224795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
224895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Replace this currently selected chip with a new chip
224995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * that uses the contact data provided.
225095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2251aca23c4de8d85b04e6044c9a8f047c337cf427c9Mindy Pereira    // Visible for testing.
2252194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /*package*/ void replaceChip(DrawableRecipientChip chip, RecipientEntry entry) {
225395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        boolean wasSelected = chip == mSelectedChip;
225495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
225595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            mSelectedChip = null;
2256cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
225795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int start = getChipStart(chip);
225895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        int end = getChipEnd(chip);
225995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        getSpannable().removeSpan(chip);
226095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        Editable editable = getText();
226195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        CharSequence chipText = createChip(entry, false);
22622d7709d276c03e536d37961076107af9f98522f5Mindy Pereira        if (chipText != null) {
22632d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            if (start == -1 || end == -1) {
22642d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                Log.e(TAG, "The chip to replace does not exist but should.");
22652d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                editable.insert(0, chipText);
22662d7709d276c03e536d37961076107af9f98522f5Mindy Pereira            } else {
22672d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                if (!TextUtils.isEmpty(chipText)) {
22682d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    // There may be a space to replace with this chip's new
2269b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp                    // associated space. Check for it
22702d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    int toReplace = end;
22712d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    while (toReplace >= 0 && toReplace < editable.length()
22722d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                            && editable.charAt(toReplace) == ' ') {
22732d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                        toReplace++;
22742d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    }
22752d7709d276c03e536d37961076107af9f98522f5Mindy Pereira                    editable.replace(start, toReplace, chipText);
227662397a5ac6b2df89ec4c2f8f23680add5850c17aMindy Pereira                }
22777ebb40ff05dbf28edd9bbed4eba7e57c8c6005aeMindy Pereira            }
2278cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
227995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        setCursorVisible(true);
228095a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (wasSelected) {
228195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            clearSelectedChip();
2282cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
228395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2284cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
228595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    /**
228695a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Handle click events for a chip. When a selected chip receives a click
228795a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * event, see if that event was in the delete icon. If so, delete it.
228895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     * Otherwise, unselect the chip.
228995a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira     */
2290194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    public void onClick(DrawableRecipientChip chip, int offset, float x, float y) {
229195a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira        if (chip.isSelected()) {
229295a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            if (isInDelete(chip, offset, x, y)) {
229395a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                removeChip(chip);
229495a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira            } else {
229595a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira                clearSelectedChip();
2296cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira            }
2297cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira        }
229895a692517516f6cdcd77dd13582e679dd59fcef7Mindy Pereira    }
2299cd61195b9be5614aefc4cda76c1732cc4840b18eMindy Pereira
230021cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira    private boolean chipsPending() {
230121cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
230221cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira    }
230321cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira
2304883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    @Override
2305883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    public void removeTextChangedListener(TextWatcher watcher) {
2306883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira        mTextWatcher = null;
2307883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira        super.removeTextChangedListener(watcher);
2308883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira    }
2309883c49973f4ea92a26cb6f40814d31f3bd389a89Mindy Pereira
231079fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    private class RecipientTextWatcher implements TextWatcher {
231172a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira
231279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
231379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void afterTextChanged(Editable s) {
231432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // If the text has been set to null or empty, make sure we remove
231532366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            // all the spans we applied.
231632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            if (TextUtils.isEmpty(s)) {
231732366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                // Remove all the chips spans.
231832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                Spannable spannable = getSpannable();
2319194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] chips = spannable.getSpans(0, getText().length(),
2320194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        DrawableRecipientChip.class);
2321194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                for (DrawableRecipientChip chip : chips) {
232232366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                    spannable.removeSpan(chip);
232332366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                }
232432366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                if (mMoreChip != null) {
232532366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                    spannable.removeSpan(mMoreChip);
232632366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                }
232741e93fbe82cd4d802e3f1fbe265038f7a0521dddKevin Lin                clearSelectedChip();
232832366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira                return;
232932366d47357b32ddd62efc03e50fc10819acd866Mindy Pereira            }
233001382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            // Get whether there are any recipients pending addition to the
233101382d764d2cbfbb4d2c1b554fe5c1ce1575dd26Mindy Pereira            // view. If there are, don't do anything in the text watcher.
233221cc0a2d9ce5c6ac5f90f51a59ccafacdb4cf7a5Mindy Pereira            if (chipsPending()) {
233379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                return;
233479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
2335c0a34aba4889151d822dd1ac0ae8b722cf5edebbMindy Pereira            // If the user is editing a chip, don't clear it.
2336f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            if (mSelectedChip != null) {
2337f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                if (!isGeneratedContact(mSelectedChip)) {
2338f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    setCursorVisible(true);
2339f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    setSelection(getText().length());
2340f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    clearSelectedChip();
2341f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                } else {
2342f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    return;
2343f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                }
234479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
234579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            int length = s.length();
234679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            // Make sure there is content there to parse and that it is
2347ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira            // not just the commit character.
234879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            if (length > 1) {
2349f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                if (lastCharacterIsCommitCharacter(s)) {
2350f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    commitByCharacter();
2351f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                    return;
2352f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                }
2353ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                char last;
235411a2adb53f764ffcadd262678ff782e6b4992decMindy Pereira                int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
2355ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                int len = length() - 1;
2356ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                if (end != len) {
2357ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                    last = s.charAt(end);
2358ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                } else {
2359ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                    last = s.charAt(len);
2360ab5a9644d4ab95fe753a07b2bdf4202d78fa8af7Mindy Pereira                }
2361f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                if (last == COMMIT_CHAR_SPACE) {
23624afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                    if (!isPhoneQuery()) {
23634afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        // Check if this is a valid email address. If it is,
23644afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        // commit it.
23654afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        String text = getText().toString();
23664afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
23674afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
23684afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                                tokenStart));
23694afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        if (!TextUtils.isEmpty(sub) && mValidator != null &&
23704afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                                mValidator.isValid(sub)) {
23714afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                            commitByCharacter();
23724afc73e1e15f7a7fdf608302b9b8488b7a4206f8Tom Taylor                        }
237379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                    }
237479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira                }
237579fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira            }
237679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
237779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
237879fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
237979fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void onTextChanged(CharSequence s, int start, int before, int count) {
2380f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            // The user deleted some text OR some text was replaced; check to
2381f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            // see if the insertion point is on a space
238272a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            // following a chip.
2383f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            if (before - count == 1) {
238472a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                // If the item deleted is a space, and the thing before the
238572a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                // space is a chip, delete the entire span.
238672a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                int selStart = getSelectionStart();
2387194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
2388194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                        DrawableRecipientChip.class);
238972a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                if (repl.length > 0) {
239072a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    // There is a chip there! Just remove it.
239172a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    Editable editable = getText();
239272a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    // Add the separator token.
239372a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    int tokenStart = mTokenizer.findTokenStart(editable, selStart);
239472a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    int tokenEnd = mTokenizer.findTokenEnd(editable, tokenStart);
239562fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    tokenEnd = tokenEnd + 1;
239662fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    if (tokenEnd > editable.length()) {
239762fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                        tokenEnd = editable.length();
239862fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    }
239962fce9316c48a188d89c31d7e616f755bd7cf14cMindy Pereira                    editable.delete(tokenStart, tokenEnd);
240072a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                    getSpannable().removeSpan(repl[0]);
240172a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira                }
24024e7d20f7b74ddce5a104138714c9683c8bd2300dMindy Pereira            } else if (count > before) {
24037e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                if (mSelectedChip != null
24047e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                    && isGeneratedContact(mSelectedChip)) {
24057e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                    if (lastCharacterIsCommitCharacter(s)) {
24067e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                        commitByCharacter();
24077e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                        return;
24087e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                    }
24097e10c86c8bead949f2f4694e97d7844c6aef9e08mindyp                }
241072a2339009029d3217a0ff39f5a92523e8fb3d41Mindy Pereira            }
241179fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
241279fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira
241379fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        @Override
241479fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2415dc04cd76d8992ad3df98dce9be5c5b6540ab3619Mindy Pereira            // Do nothing.
241679fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira        }
241779fb4360e567c5ce9e80142af2807126d3bb67f0Mindy Pereira    }
24181852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
2419c1b5258fe0d244be2d4d8c4d2969c1b8859c6fa8Alice Yang   public boolean lastCharacterIsCommitCharacter(CharSequence s) {
2420f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        char last;
2421f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
2422f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        int len = length() - 1;
2423f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        if (end != len) {
2424f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            last = s.charAt(end);
2425f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        } else {
2426f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            last = s.charAt(len);
2427f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        }
2428f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return last == COMMIT_CHAR_COMMA || last == COMMIT_CHAR_SEMICOLON;
2429f30a42800318f6790d55421f8f6980eb38db4d3cmindyp    }
2430f30a42800318f6790d55421f8f6980eb38db4d3cmindyp
2431194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    public boolean isGeneratedContact(DrawableRecipientChip chip) {
2432f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        long contactId = chip.getContactId();
2433f30a42800318f6790d55421f8f6980eb38db4d3cmindyp        return contactId == RecipientEntry.INVALID_CONTACT
2434f30a42800318f6790d55421f8f6980eb38db4d3cmindyp                || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT);
2435f30a42800318f6790d55421f8f6980eb38db4d3cmindyp    }
2436f30a42800318f6790d55421f8f6980eb38db4d3cmindyp
2437e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2438e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
2439e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2440e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private void handlePasteClip(ClipData clip) {
2441e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        removeTextChangedListener(mTextWatcher);
2442e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2443e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
2444e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            for (int i = 0; i < clip.getItemCount(); i++) {
2445e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                CharSequence paste = clip.getItemAt(i).getText();
2446e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                if (paste != null) {
2447e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    int start = getSelectionStart();
2448e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    int end = getSelectionEnd();
2449e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    Editable editable = getText();
2450e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    if (start >= 0 && end >= 0 && start != end) {
2451e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                        editable.append(paste, start, end);
2452e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    } else {
2453e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                        editable.insert(end, paste);
2454e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    }
2455e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                    handlePasteAndReplace();
2456e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                }
2457e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            }
2458e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2459e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2460e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        mHandler.post(mAddTextWatcher);
2461e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2462e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
246320c9d620e79ae28994856541761a951074551518Mindy Pereira    @Override
246420c9d620e79ae28994856541761a951074551518Mindy Pereira    public boolean onTextContextMenuItem(int id) {
246520c9d620e79ae28994856541761a951074551518Mindy Pereira        if (id == android.R.id.paste) {
246620c9d620e79ae28994856541761a951074551518Mindy Pereira            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
246720c9d620e79ae28994856541761a951074551518Mindy Pereira                    Context.CLIPBOARD_SERVICE);
2468e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            handlePasteClip(clipboard.getPrimaryClip());
246920c9d620e79ae28994856541761a951074551518Mindy Pereira            return true;
247020c9d620e79ae28994856541761a951074551518Mindy Pereira        }
247120c9d620e79ae28994856541761a951074551518Mindy Pereira        return super.onTextContextMenuItem(id);
247220c9d620e79ae28994856541761a951074551518Mindy Pereira    }
247320c9d620e79ae28994856541761a951074551518Mindy Pereira
2474b4e244af14950aee7d612612d5406981315d3454Mindy Pereira    private void handlePasteAndReplace() {
2475194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        ArrayList<DrawableRecipientChip> created = handlePaste();
2476b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        if (created != null && created.size() > 0) {
2477b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            // Perform reverse lookups on the pasted contacts.
2478b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            IndividualReplacementTask replace = new IndividualReplacementTask();
2479b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            replace.execute(created);
2480b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        }
2481b4e244af14950aee7d612612d5406981315d3454Mindy Pereira    }
2482b4e244af14950aee7d612612d5406981315d3454Mindy Pereira
248320c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
2484194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    /* package */ArrayList<DrawableRecipientChip> handlePaste() {
248520c9d620e79ae28994856541761a951074551518Mindy Pereira        String text = getText().toString();
248620c9d620e79ae28994856541761a951074551518Mindy Pereira        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
248720c9d620e79ae28994856541761a951074551518Mindy Pereira        String lastAddress = text.substring(originalTokenStart);
248820c9d620e79ae28994856541761a951074551518Mindy Pereira        int tokenStart = originalTokenStart;
24890cae85010120a2a8490baf3488ed0bd2293e1d4djli        int prevTokenStart = 0;
2490194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip findChip = null;
2491194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        ArrayList<DrawableRecipientChip> created = new ArrayList<DrawableRecipientChip>();
249220c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenStart != 0) {
249320c9d620e79ae28994856541761a951074551518Mindy Pereira            // There are things before this!
24940cae85010120a2a8490baf3488ed0bd2293e1d4djli            while (tokenStart != 0 && findChip == null && tokenStart != prevTokenStart) {
249520c9d620e79ae28994856541761a951074551518Mindy Pereira                prevTokenStart = tokenStart;
249620c9d620e79ae28994856541761a951074551518Mindy Pereira                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
249720c9d620e79ae28994856541761a951074551518Mindy Pereira                findChip = findChip(tokenStart);
2498fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor                if (tokenStart == originalTokenStart && findChip == null) {
2499fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor                    break;
2500fda0170c661e237c202a8519baba1ce3294c6afeTom Taylor                }
250120c9d620e79ae28994856541761a951074551518Mindy Pereira            }
250220c9d620e79ae28994856541761a951074551518Mindy Pereira            if (tokenStart != originalTokenStart) {
250320c9d620e79ae28994856541761a951074551518Mindy Pereira                if (findChip != null) {
250420c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenStart = prevTokenStart;
250520c9d620e79ae28994856541761a951074551518Mindy Pereira                }
250620c9d620e79ae28994856541761a951074551518Mindy Pereira                int tokenEnd;
2507194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                DrawableRecipientChip createdChip;
250820c9d620e79ae28994856541761a951074551518Mindy Pereira                while (tokenStart < originalTokenStart) {
2509b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(getText().toString(),
2510b5afbc70a27f7534fd6dfa7440b3a3450bbc52cfmindyp                            tokenStart));
251120c9d620e79ae28994856541761a951074551518Mindy Pereira                    commitChip(tokenStart, tokenEnd, getText());
251220c9d620e79ae28994856541761a951074551518Mindy Pereira                    createdChip = findChip(tokenStart);
2513c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira                    if (createdChip == null) {
2514c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira                        break;
2515c52ea8e900948bf42ed63b2d3d6c9afbd70d9df0Mindy Pereira                    }
251620c9d620e79ae28994856541761a951074551518Mindy Pereira                    // +1 for the space at the end.
251720c9d620e79ae28994856541761a951074551518Mindy Pereira                    tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
2518b4e244af14950aee7d612612d5406981315d3454Mindy Pereira                    created.add(createdChip);
251920c9d620e79ae28994856541761a951074551518Mindy Pereira                }
252020c9d620e79ae28994856541761a951074551518Mindy Pereira            }
252120c9d620e79ae28994856541761a951074551518Mindy Pereira        }
252220c9d620e79ae28994856541761a951074551518Mindy Pereira        // Take a look at the last token. If the token has been completed with a
252320c9d620e79ae28994856541761a951074551518Mindy Pereira        // commit character, create a chip.
252420c9d620e79ae28994856541761a951074551518Mindy Pereira        if (isCompletedToken(lastAddress)) {
252520c9d620e79ae28994856541761a951074551518Mindy Pereira            Editable editable = getText();
2526b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
2527b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            commitChip(tokenStart, editable.length(), editable);
2528b4e244af14950aee7d612612d5406981315d3454Mindy Pereira            created.add(findChip(tokenStart));
252920c9d620e79ae28994856541761a951074551518Mindy Pereira        }
2530b4e244af14950aee7d612612d5406981315d3454Mindy Pereira        return created;
253120c9d620e79ae28994856541761a951074551518Mindy Pereira    }
253220c9d620e79ae28994856541761a951074551518Mindy Pereira
253320c9d620e79ae28994856541761a951074551518Mindy Pereira    // Visible for testing.
253420c9d620e79ae28994856541761a951074551518Mindy Pereira    /* package */int movePastTerminators(int tokenEnd) {
253520c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenEnd >= length()) {
253620c9d620e79ae28994856541761a951074551518Mindy Pereira            return tokenEnd;
253720c9d620e79ae28994856541761a951074551518Mindy Pereira        }
253820c9d620e79ae28994856541761a951074551518Mindy Pereira        char atEnd = getText().toString().charAt(tokenEnd);
253920c9d620e79ae28994856541761a951074551518Mindy Pereira        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
254020c9d620e79ae28994856541761a951074551518Mindy Pereira            tokenEnd++;
254120c9d620e79ae28994856541761a951074551518Mindy Pereira        }
254220c9d620e79ae28994856541761a951074551518Mindy Pereira        // This token had not only an end token character, but also a space
254320c9d620e79ae28994856541761a951074551518Mindy Pereira        // separating it from the next token.
254420c9d620e79ae28994856541761a951074551518Mindy Pereira        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
254520c9d620e79ae28994856541761a951074551518Mindy Pereira            tokenEnd++;
254620c9d620e79ae28994856541761a951074551518Mindy Pereira        }
254720c9d620e79ae28994856541761a951074551518Mindy Pereira        return tokenEnd;
254820c9d620e79ae28994856541761a951074551518Mindy Pereira    }
254920c9d620e79ae28994856541761a951074551518Mindy Pereira
25501852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
2551194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        private DrawableRecipientChip createFreeChip(RecipientEntry entry) {
25521852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            try {
2553f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                if (mNoChips) {
2554f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                    return null;
2555f6519d2aabaac51fd62dc819c109db86748d79e8Mindy Pereira                }
2556f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                return constructChipSpan(entry, false,
25575e60c2b9b39b0aec39e3a03f9a178c2cb857b9d6mindyp                        false /*leave space for contact icon */);
25581852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            } catch (NullPointerException e) {
25591852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                Log.e(TAG, e.getMessage(), e);
25601852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                return null;
25611852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
25621852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
25631852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
25641852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        @Override
256578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        protected void onPreExecute() {
256678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            // Ensure everything is in chip-form already, so we don't have text that slowly gets
256778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            // replaced
2568194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final List<DrawableRecipientChip> originalRecipients =
2569194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    new ArrayList<DrawableRecipientChip>();
2570194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final DrawableRecipientChip[] existingChips = getSortedRecipients();
257178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            for (int i = 0; i < existingChips.length; i++) {
257278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                originalRecipients.add(existingChips[i]);
257378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
257478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            if (mRemovedSpans != null) {
257578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                originalRecipients.addAll(mRemovedSpans);
257678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
257778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
2578194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final List<DrawableRecipientChip> replacements =
2579194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    new ArrayList<DrawableRecipientChip>(originalRecipients.size());
258078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
2581194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            for (final DrawableRecipientChip chip : originalRecipients) {
258278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                if (RecipientEntry.isCreatedRecipient(chip.getEntry().getContactId())
258378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                        && getSpannable().getSpanStart(chip) != -1) {
258478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    replacements.add(createFreeChip(chip.getEntry()));
258578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                } else {
258678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    replacements.add(null);
258778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                }
258878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
258978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
259078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            processReplacements(originalRecipients, replacements);
259178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        }
259278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
259378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        @Override
25941852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        protected Void doInBackground(Void... params) {
25951852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            if (mIndividualReplacements != null) {
25961852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                mIndividualReplacements.cancel(true);
25971852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
25981852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // For each chip in the list, look up the matching contact.
25991852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // If there is a match, replace that chip with the matching
26001852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // chip.
2601194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final ArrayList<DrawableRecipientChip> recipients =
2602194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                    new ArrayList<DrawableRecipientChip>();
2603194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip[] existingChips = getSortedRecipients();
26041852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (int i = 0; i < existingChips.length; i++) {
260578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                recipients.add(existingChips[i]);
26061852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
26071852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            if (mRemovedSpans != null) {
260878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                recipients.addAll(mRemovedSpans);
26091852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
261003cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira            ArrayList<String> addresses = new ArrayList<String>();
2611194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip;
261278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            for (int i = 0; i < recipients.size(); i++) {
261378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                chip = recipients.get(i);
2614f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                if (chip != null) {
261503cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira                    addresses.add(createAddressText(chip.getEntry()));
2616f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                }
26171852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
26187a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy            final BaseRecipientAdapter adapter = getAdapter();
261976f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
26206b7110f320c978c368c28bdb06212c6a6df12f1fAlice Yang                    adapter.getAccount(), new RecipientMatchCallback() {
262116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                        @Override
262294fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        public void matchesFound(Map<String, RecipientEntry> entries) {
2623194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            final ArrayList<DrawableRecipientChip> replacements =
2624194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                                    new ArrayList<DrawableRecipientChip>();
2625194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            for (final DrawableRecipientChip temp : recipients) {
262616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                RecipientEntry entry = null;
2627f0579ee4ab41c021f20f78b25ecf4c526b308ec6Alice Yang                                if (temp != null && RecipientEntry.isCreatedRecipient(
2628f0579ee4ab41c021f20f78b25ecf4c526b308ec6Alice Yang                                        temp.getEntry().getContactId())
262916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        && getSpannable().getSpanStart(temp) != -1) {
263016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    // Replace this.
263116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    entry = createValidatedEntry(
263216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            entries.get(tokenizeAddress(temp.getEntry()
263316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                                    .getDestination())));
263416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                }
263516923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                if (entry != null) {
263616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    replacements.add(createFreeChip(entry));
263716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                } else {
263816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    replacements.add(null);
263916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                }
264016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                            }
264178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            processReplacements(recipients, replacements);
26421852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                        }
264394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy
264494fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        @Override
2645f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                        public void matchesNotFound(final Set<String> unfoundAddresses) {
2646194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            final List<DrawableRecipientChip> replacements =
2647194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                                    new ArrayList<DrawableRecipientChip>(unfoundAddresses.size());
264894fa301de71cc110671802eba8376c275b4055a4Scott Kennedy
2649194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            for (final DrawableRecipientChip temp : recipients) {
2650ad8781e6062f086aa4160eab2799b6848911aa3dAlice Yang                                if (temp != null && RecipientEntry.isCreatedRecipient(
2651ad8781e6062f086aa4160eab2799b6848911aa3dAlice Yang                                        temp.getEntry().getContactId())
265294fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                        && getSpannable().getSpanStart(temp) != -1) {
2653f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                                    if (unfoundAddresses.contains(
2654f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                                            temp.getEntry().getDestination())) {
265594fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                        replacements.add(createFreeChip(temp.getEntry()));
265694fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                    } else {
265794fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                        replacements.add(null);
265894fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                    }
265994fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                } else {
266094fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                    replacements.add(null);
266194fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                                }
266294fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                            }
266394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy
266478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            processReplacements(recipients, replacements);
266594fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        }
266616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                    });
26671852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            return null;
26681852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
266978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
2670194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        private void processReplacements(final List<DrawableRecipientChip> recipients,
2671194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                final List<DrawableRecipientChip> replacements) {
267278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            if (replacements != null && replacements.size() > 0) {
267378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                final Runnable runnable = new Runnable() {
267478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    @Override
267578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    public void run() {
26760e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                        final Editable text = new SpannableStringBuilder(getText());
267778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                        int i = 0;
26780e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                        for (final DrawableRecipientChip chip : recipients) {
26790e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                            final DrawableRecipientChip replacement = replacements.get(i);
268078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            if (replacement != null) {
268178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                final RecipientEntry oldEntry = chip.getEntry();
268278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                final RecipientEntry newEntry = replacement.getEntry();
268378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                final boolean isBetter =
268478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        RecipientAlternatesAdapter.getBetterRecipient(
268578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                oldEntry, newEntry) == newEntry;
268678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
268778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                if (isBetter) {
268878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                    // Find the location of the chip in the text currently shown.
26890e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                    final int start = text.getSpanStart(chip);
269078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                    if (start != -1) {
269178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // Replacing the entirety of what the chip represented,
269278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // including the extra space dividing it from other chips.
2693b023f69b1f35199bc17f1d2708acb492d3ae49b0Paul Westbrook                                        final int end =
2694b023f69b1f35199bc17f1d2708acb492d3ae49b0Paul Westbrook                                                Math.min(text.getSpanEnd(chip) + 1, text.length());
26950e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                        text.removeSpan(chip);
269678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // Make sure we always have just 1 space at the end to
269778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // separate this chip from the next chip.
26980e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                        final SpannableString displayText =
269978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                new SpannableString(createAddressText(
27000e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                                        replacement.getEntry()).trim() + " ");
270178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        displayText.setSpan(replacement, 0,
270278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                displayText.length() - 1,
270378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
270478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // Replace the old text we found with with the new display
270578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // text, which now may also contain the display name of the
270678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        // recipient.
27070e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                                        text.replace(start, end, displayText);
270878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        replacement.setOriginalText(displayText.toString());
270978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        replacements.set(i, null);
271078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
271178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                        recipients.set(i, replacement);
271278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                    }
271378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                                }
271478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            }
271578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                            i++;
271678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                        }
27170e128968f242866568bba0f833bb17ffda127f07Scott Kennedy                        setText(text);
271878f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    }
271978f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                };
272078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy
272178f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                if (Looper.myLooper() == Looper.getMainLooper()) {
272278f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    runnable.run();
272378f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                } else {
272478f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                    mHandler.post(runnable);
272578f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy                }
272678f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy            }
272778f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy        }
27281852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    }
27291852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira
273078f38a09c9753c0ac1838ce0bfd3a6bc1a307ff7Scott Kennedy    private class IndividualReplacementTask
2731194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            extends AsyncTask<ArrayList<DrawableRecipientChip>, Void, Void> {
27321852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        @Override
2733194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        protected Void doInBackground(ArrayList<DrawableRecipientChip>... params) {
27341852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // For each chip in the list, look up the matching contact.
27351852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // If there is a match, replace that chip with the matching
27361852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            // chip.
2737194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            final ArrayList<DrawableRecipientChip> originalRecipients = params[0];
273803cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira            ArrayList<String> addresses = new ArrayList<String>();
2739194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy            DrawableRecipientChip chip;
27401852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            for (int i = 0; i < originalRecipients.size(); i++) {
2741f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                chip = originalRecipients.get(i);
2742f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                if (chip != null) {
274303cfe3eee5635e419ab1d70d463b2b8beac72f00Mindy Pereira                    addresses.add(createAddressText(chip.getEntry()));
2744f07e8b2db488aa308f0ca2916dd53cbd1b898c6fMindy Pereira                }
27451852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            }
27467a4e67708498ec46c2e9b3bad69d3807d88c064eScott Kennedy            final BaseRecipientAdapter adapter = getAdapter();
274776f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
2748858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy                    adapter.getAccount(),
274916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                    new RecipientMatchCallback() {
275016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp
275116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                        @Override
275294fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        public void matchesFound(Map<String, RecipientEntry> entries) {
2753194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy                            for (final DrawableRecipientChip temp : originalRecipients) {
275416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                if (RecipientEntry.isCreatedRecipient(temp.getEntry()
275516923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        .getContactId())
275616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        && getSpannable().getSpanStart(temp) != -1) {
275716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    // Replace this.
275876f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert                                    final RecipientEntry entry = createValidatedEntry(entries
275916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            .get(tokenizeAddress(temp.getEntry().getDestination())
276016923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                                    .toLowerCase()));
276176f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert                                    if (entry != null) {
276216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        mHandler.post(new Runnable() {
276316923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            @Override
276416923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            public void run() {
276576f1f2d4eb66956814254b0e4f86da7aa8261bcbAlon Albert                                                replaceChip(temp, entry);
276616923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                            }
276716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                        });
276816923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                    }
276916923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                                }
27701852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira                            }
277116923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                        }
277216923ee63a79fce4be3f62b08bcd1f80617c1205mindyp
277394fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        @Override
2774f7e202d8b83bfbd73ca47ba7843ebc4dd57c2fa4Scott Kennedy                        public void matchesNotFound(final Set<String> unfoundAddresses) {
277594fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                            // No action required
277694fa301de71cc110671802eba8376c275b4055a4Scott Kennedy                        }
277716923ee63a79fce4be3f62b08bcd1f80617c1205mindyp                    });
27781852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira            return null;
27791852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira        }
27801852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira    }
27811d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
2782e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
2783e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    /**
2784e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     * MoreImageSpan is a simple class created for tracking the existence of a
2785e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     * more chip across activity restarts/
2786e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira     */
2787e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    private class MoreImageSpan extends ImageSpan {
2788e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        public MoreImageSpan(Drawable b) {
2789e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira            super(b);
2790e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira        }
2791e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira    }
2792e1bfc9218326c02ff51985dcb22ee9b0ae385807Mindy Pereira
27931d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
27941d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onDown(MotionEvent e) {
27951d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
27961d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
27971d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
27981d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
27991d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
28001d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
28011d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
28021d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
28031d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
28041d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
28051d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onLongPress(MotionEvent event) {
28061d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        if (mSelectedChip != null) {
28071d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira            return;
28081d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
28091d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        float x = event.getX();
28101d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        float y = event.getY();
28111650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int offset = putOffsetInRange(x, y);
2812194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        DrawableRecipientChip currentChip = findChip(offset);
28131d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        if (currentChip != null) {
2814e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            if (mDragEnabled) {
2815e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Start drag-and-drop for the selected chip.
2816e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                startDrag(currentChip);
2817e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            } else {
2818e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Copy the selected chip email address.
2819e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                showCopyDialog(currentChip.getEntry().getDestination());
2820e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            }
2821e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2822e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2823e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
28241650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    // The following methods are used to provide some functionality on older versions of Android
28251650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    // These methods were copied out of JB MR2's TextView
28261650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    /////////////////////////////////////////////////
28271650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int supportGetOffsetForPosition(float x, float y) {
28281650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        if (getLayout() == null) return -1;
28291650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int line = supportGetLineAtCoordinate(y);
28301650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        final int offset = supportGetOffsetAtCoordinate(line, x);
28311650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return offset;
28321650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28331650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
28341650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private float supportConvertToLocalHorizontalCoordinate(float x) {
28351650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x -= getTotalPaddingLeft();
28361650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        // Clamp the position to inside of the view.
28371650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x = Math.max(0.0f, x);
28381650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
28391650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x += getScrollX();
28401650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return x;
28411650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28421650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
28431650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int supportGetLineAtCoordinate(float y) {
28441650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y -= getTotalPaddingLeft();
28451650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        // Clamp the position to inside of the view.
28461650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y = Math.max(0.0f, y);
28471650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
28481650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        y += getScrollY();
28491650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return getLayout().getLineForVertical((int) y);
28501650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28511650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
28521650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    private int supportGetOffsetAtCoordinate(int line, float x) {
28531650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        x = supportConvertToLocalHorizontalCoordinate(x);
28541650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy        return getLayout().getOffsetForHorizontal(line, x);
28551650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    }
28561650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy    /////////////////////////////////////////////////
28571650ef6bb75ba88340cc59d22b2578c0f460b4f1Scott Kennedy
2858e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2859e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Enables drag-and-drop for chips.
2860e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2861e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    public void enableDrag() {
2862e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        mDragEnabled = true;
2863e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2864e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2865e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2866e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Starts drag-and-drop for the selected chip.
2867e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2868194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy    private void startDrag(DrawableRecipientChip currentChip) {
2869e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        String address = currentChip.getEntry().getDestination();
2870e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
2871e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2872e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // Start drag mode.
2873e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
2874e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2875e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // Remove the current chip, so drag-and-drop will result in a move.
2876e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        // TODO (phamm): consider readd this chip if it's dropped outside a target.
2877e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        removeChip(currentChip);
2878e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2879e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2880e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2881e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Handles drag event.
2882e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2883e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    @Override
2884e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    public boolean onDragEvent(DragEvent event) {
2885e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        switch (event.getAction()) {
2886e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DRAG_STARTED:
2887e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                // Only handle plain text drag and drop.
2888e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
2889e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DRAG_ENTERED:
2890e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                requestFocus();
2891e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return true;
2892e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            case DragEvent.ACTION_DROP:
2893e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                handlePasteClip(event.getClipData());
2894e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham                return true;
2895e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2896e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        return false;
2897e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    }
2898e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2899e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    /**
2900e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     * Drag shadow for a {@link RecipientChip}.
2901e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham     */
2902e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham    private final class RecipientChipShadow extends DragShadowBuilder {
2903194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        private final DrawableRecipientChip mChip;
2904e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2905194d427ebcfc2133bda410e0e4c399250d9a6066Scott Kennedy        public RecipientChipShadow(DrawableRecipientChip chip) {
2906e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            mChip = chip;
2907e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2908e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2909e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        @Override
2910e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
2911f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            Rect rect = mChip.getBounds();
2912e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            shadowSize.set(rect.width(), rect.height());
2913e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham            shadowTouchPoint.set(rect.centerX(), rect.centerY());
2914e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        }
2915e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham
2916e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        @Override
2917e51c7226dfb6420b0d4c1a7bfdf3217ee4e560a6Minh Pham        public void onDrawShadow(Canvas canvas) {
2918f30a42800318f6790d55421f8f6980eb38db4d3cmindyp            mChip.draw(canvas);
29191d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        }
29201d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29211d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29221d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    private void showCopyDialog(final String address) {
2923fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        if (!mAttachedToWindow) {
2924fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler            return;
2925fbeca0eb27de966dcac8a48d1d0e888e565bf255Tony Mantler        }
29261d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyAddress = address;
29271d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setTitle(address);
292876e62e334ef2b9ec55e5395f8374072f7ee1ea84Mindy Pereira        mCopyDialog.setContentView(R.layout.copy_chip_dialog_layout);
29291d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setCancelable(true);
29301d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setCanceledOnTouchOutside(true);
293180f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        Button button = (Button)mCopyDialog.findViewById(android.R.id.button1);
293280f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        button.setOnClickListener(this);
293380f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        int btnTitleId;
2934c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor        if (isPhoneQuery()) {
293580f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor            btnTitleId = R.string.copy_number;
293680f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        } else {
293780f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor            btnTitleId = R.string.copy_email;
293880f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        }
293980f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        String buttonTitle = getContext().getResources().getString(btnTitleId);
294080f4abfb682426384e88fb1dddc682be1c8a6c7fTom Taylor        button.setText(buttonTitle);
29411d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.setOnDismissListener(this);
29421d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.show();
29431d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29441d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29451d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29461d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
29471d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
29481d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
29491d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29501d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29511d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29521d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onShowPress(MotionEvent e) {
29531d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
29541d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29551d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29561d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29571d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public boolean onSingleTapUp(MotionEvent e) {
29581d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Do nothing.
29591d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        return false;
29601d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29611d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29621d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29631d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onDismiss(DialogInterface dialog) {
29641d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyAddress = null;
29651d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
29661d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira
29671d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    @Override
29681d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    public void onClick(View v) {
29691d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        // Copy this to the clipboard.
29701d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
29711d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira                Context.CLIPBOARD_SERVICE);
29721d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
29731d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira        mCopyDialog.dismiss();
29741d1ec857e5d050163cb87936e233b5f9cf321344Mindy Pereira    }
2975c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor
2976c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor    protected boolean isPhoneQuery() {
2977ca5227122474fb124a4fe573a2f07e14d7a33f3eMindy Pereira        return getAdapter() != null
2978858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy                && getAdapter().getQueryType() == BaseRecipientAdapter.QUERY_TYPE_PHONE;
2979858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy    }
2980858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy
2981858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy    @Override
2982858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy    public BaseRecipientAdapter getAdapter() {
2983858e094f1c695aefdf6a23f522c0f16d81bd79f7Scott Kennedy        return (BaseRecipientAdapter) super.getAdapter();
2984c7a87f0ad6a8f722ba93cb7c457ed1a1be5ab3b5Tom Taylor    }
29851852931de1e24e77cb708f4ba010eaa269426657Mindy Pereira}
2986