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