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