RecipientEditTextView.java revision 3e9631982511d9692e4bc0dbd724240fae91cf1f
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/* 22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project 32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License. 62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at 72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software 112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and 142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License. 152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips; 182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 19b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.app.Dialog; 20b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipData; 2149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereiraimport android.content.ClipDescription; 22b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager; 232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context; 24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface; 25b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener; 26c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap; 271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory; 28c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas; 291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix; 303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.graphics.Point; 31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 33c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 3577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask; 36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler; 37156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message; 3822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable; 392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 40572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 48c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 50c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 532d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 54c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 56a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.view.DragEvent; 58b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 60c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 65572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 66416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 69b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 70c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 71156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 73e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow; 74416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 75c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 76b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 77a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 79b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 8277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 90b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 91b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 92e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener, 93e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira PopupWindow.OnDismissListener { 94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 9600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 9700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 9800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 9900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 10000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 10400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 10500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final long DISMISS_DELAY = 300; 10600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 1074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 1084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 1094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 11000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static int sSelectedTextColor = -1; 11100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 11200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Resources for displaying chips. 113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 115c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Drawable mInvalidChipBackground; 11800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 11900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Drawable mChipBackgroundPressed; 12000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 12100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private float mChipHeight; 12200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 12300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private float mChipFontSize; 12400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Validator mValidator; 130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 139c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 141054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 142a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 143156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 144156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 145c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 146c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 147b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 148b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 15177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 15277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 155bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 156bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 167a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 168a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 170b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 171b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 17300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 17600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Obtain the enclosing scroll view, if it exists, so that the view can be 17700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // scrolled to show the last line of chips content. 178416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 179416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 18000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private boolean mTriedGettingScrollView; 181416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1823e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private boolean mDragEnabled = false; 1833e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 186e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 187e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 188e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 189e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 190e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 191e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 192e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 193e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 19477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 19577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 197a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 198a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 199a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 200a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 201a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 202a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 203a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 204a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira private Runnable mDelayedShrink = new Runnable() { 206a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 207a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira @Override 208a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira public void run() { 209a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira shrink(); 210a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 211a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 212a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira }; 213a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 2142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 2152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 21677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 21777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 21877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 220e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAlternatesPopup.setOnDismissListener(this); 2211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 222e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAddressPopup.setOnDismissListener(this); 2230436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira mCopyDialog = new Dialog(context); 224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 226b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 228368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 229e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 233368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 234b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 235b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 236b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 239c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 241156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 243156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 244156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 246156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 247156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 248156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 249156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 250156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2516ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2526ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 253b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 254ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 255cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 25600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ RecipientChip getLastChip() { 25700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip last = null; 25800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 25900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (chips != null && chips.length > 0) { 26000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira last = chips[chips.length - 1]; 261ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 26200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return last; 2634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2654fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2664fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2674fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 26900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip last = getLastChip(); 27000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (last != null) { 27100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Grab the last chip and set the cursor to after it. 27200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length())); 273d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 274d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 275d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 276d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira @Override 27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira public void onRestoreInstanceState(Parcelable state) { 27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (!TextUtils.isEmpty(getText())) { 28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(null); 28122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } else { 28222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(state); 28322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 28600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira @Override 2877c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira public Parcelable onSaveInstanceState() { 2887c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira // If the user changes orientation while they are editing, just roll back the selection. 2897c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira clearSelectedChip(); 2907c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira return super.onSaveInstanceState(); 2917c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira } 2927c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira 293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 295c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 296c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 297c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 298c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 300c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 301001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 302001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 303001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 304001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 3074f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 30805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 311c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 312a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 313c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 314c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 315a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 316a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 317a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 318a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 3196ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 320c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 321c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 322d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 323d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 324b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 325d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 3274fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 3284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 329416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 3304fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3312d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 333b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 334b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 335b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 336b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 337b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3395a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3405a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 343a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (getWidth() <= 0) { 344a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // We don't have the width yet which means the view hasn't been drawn yet 345a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // and there is no reason to attempt to commit chips yet. 346a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This focus lost must be the result of an orientation change 347a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // or an initial rendering. 348a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Re-post the shrink for later. 349a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.removeCallbacks(mDelayedShrink); 350a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.post(mDelayedShrink); 351a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira return; 352a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 353001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 354001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 355001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 356a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 359001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 360001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 361001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 362001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira Editable text = getText(); 364a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(text, start); 365a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This token was already tokenized, so skip past the ending token. 3668a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira if (whatEnd < text.length() && text.charAt(whatEnd) == ',') { 367a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira whatEnd++; 368a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 371a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int selEnd = getSelectionEnd(); 372a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) { 373001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 374001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 375001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 376001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 377001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 378090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 379001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 38977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 39077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 39177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 39277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 39377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 39477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 398e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 399c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 400c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 401c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 402c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 403c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 410e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 41200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 4231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 4241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 4251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 42777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 42897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 42997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 43097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 432f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 433f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 434f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 435f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 436f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 437f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 445045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 446e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 450e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 45200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4541a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4551a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 462c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 463045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 464045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 465045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 466045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4689024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4709024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 47190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 47290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 47390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 47490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 47690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 47990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 488ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira if (photo != null) { 489ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 490ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Rect backgroundPadding = new Rect(); 491ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground.getPadding(backgroundPadding); 492ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 0 + backgroundPadding.top, 494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira width - backgroundPadding.right, 495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira height - backgroundPadding.bottom); 496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Matrix matrix = new Matrix(); 497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 500c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5019024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 5029024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 503c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 504e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 50597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 506379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 50797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 508c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 5101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 5111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 5121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 51400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** 51500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira * Get the background drawable for a RecipientChip. 51600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira */ 51700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 51800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ Drawable getChipBackground(RecipientEntry contact) { 51900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 52000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mChipBackground : mInvalidChipBackground; 52100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 52200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 52397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 52497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 5253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham paint.getTextBounds(text, 0, text.length(), bounds); 52697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 52797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 52897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 52997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 53000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 5311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 5321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 5331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 5341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 535c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 5361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 537c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 54077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 544e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 547045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 55377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 55677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 557c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5582d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5592d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 561045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 562045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5635519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5645519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 566c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5675519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 568416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 56997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 57097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 571f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 572f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5802d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5812d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5834f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5844f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5854f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5874f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5901426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5911e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5921426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 593c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 594b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 595b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 596c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 59743876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 598045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 599d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 610b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 611b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 612b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 613b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 615a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void setMoreItem(TextView moreItem) { 616a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mMoreItem = moreItem; 617a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 618a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 619ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 620ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 621ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipBackground(Drawable chipBackground) { 622ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground = chipBackground; 623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 626ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipHeight(int height) { 627ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipHeight = height; 628ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 629ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 631bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 632bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 633bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 637bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 638bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 639bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 640bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 642c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 643c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 64453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (width != 0 && height != 0) { 64553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount > 0) { 64653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira postHandlePendingChips(); 64753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } else { 64853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira checkChipWidths(); 64953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 6507bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 65177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 65200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (mScrollView == null && !mTriedGettingScrollView) { 653416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 654416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 655416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 656416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 658416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 659416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 66000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mTriedGettingScrollView = true; 661416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 662c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 663c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 664a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 665a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 666a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 667a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 668a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void checkChipWidths() { 67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Check the widths of the associated chips. 67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (chips != null) { 67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira Rect bounds; 67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira for (RecipientChip chip : chips) { 67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira bounds = chip.getDrawable().getBounds(); 67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) { 67753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Need to redraw that chip. 67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira replaceChip(chip, chip.getEntry()); 67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 681a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 68253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 68353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void handlePendingChips() { 6857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6877bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6887bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6897bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 69253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount <= 0) { 69353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira return; 69453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 69553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 710a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 7110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 7130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 71400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira sanitizeEnd(); 71522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 719a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 721a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 722a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 72577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 726a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 727a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 728a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 729a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 73022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 73277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 73377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 734a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 735a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 74200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 74300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void sanitizeEnd() { 7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 74500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 74922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 75300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira lastSpan = getLastChip(); 7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 7710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 7720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 7741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 7751e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7771e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 779b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 7810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 7820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 7830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 78600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String destText = createAddressText(entry); 787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 788b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = destText.length() - 1; 789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 792d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 798d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 799d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 800d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 801d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 80222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 80322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 80422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 8056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 806d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 807d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 80877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 8090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8100fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 8110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 812d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 813d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 814d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 8176ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 818454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 819454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 8201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 8211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 8221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 8236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 8246ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 8256ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 8266ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 8276ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 8281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 8316ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 8336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 8346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 837a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 838a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 845a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 846a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 847a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 848a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 849490556a764a879cd0eaff358e90705cc1335c92eErik } 850d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8516ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 852454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 853a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 854a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 8551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 8576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 8586ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 8596ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 8606ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 8611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 8621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 8631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 8641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 8650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 8670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 869c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 871c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 872c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 873c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 874c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 8838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 8848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 886c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 887c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 888c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 889c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 89595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 89695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 89795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 89895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 89995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 90095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 90195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 9068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 91095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 912c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 916d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 917c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 919e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 921e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 922e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 923e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 925c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 92695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 927e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 929e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 934e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 935e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 936e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 937e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 940c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 941c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 947e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 948e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 949e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 950e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 951045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 952045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 953045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 954045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 955045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 957045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 9588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 9594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 960045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 9614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 962dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 966e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 9704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 9824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 983054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 9854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 98749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ListAdapter adapter = getAdapter(); 98849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (adapter != null && adapter.getCount() > 0 && enoughToFilter() 98949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira && end == getSelectionEnd()) { 990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 992e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 993e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 994e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 995e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 99649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (editable.length() > tokenEnd + 1) { 99749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char charAt = editable.charAt(tokenEnd + 1); 99849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) { 99949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 100049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1001a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 1002b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 1003e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 1004e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 10051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 1006d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 1007d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 1008d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 100971fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira if (chipText != null && start > -1 && end > -1) { 10105ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 10115ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 1012d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 101349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Only dismiss the dropdown if it is related to the text we 101449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // just committed. 101549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // For paste, it may not be as there are possibly multiple 101649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // tokens being added. 101749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (end == getSelectionEnd()) { 101849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira dismissDropDown(); 101949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1020f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 10214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 1022d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1023d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 10244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 1025d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1026d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 10274031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10284031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ void sanitizeBetween() { 1029f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 10304031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira RecipientChip[] recips = getSortedRecipients(); 1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 1034f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1037f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 1038f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 1039f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 1040f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1041c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira Editable text = getText(); 10424031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1043c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira // There is nothing after this chip. 1044c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira return; 1045c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira } 1046c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira if (text.charAt(startLooking) == ' ') { 1047f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 1048f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1049f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1050a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira if (startLooking >= 0 && end >= 0 && startLooking != end) { 1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 1052f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1053f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1054f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1055f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 1056e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 10571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 10581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 10591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 10601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 10621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 10631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 106405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 10651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 1066e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1067e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1068e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 106939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 107039f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 107139f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 107239f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 107339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 1074e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 1075e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 1076e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 1077e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 1078e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 10793b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 10803b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 10813b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 10823b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 1083a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1084a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, getSelectionEnd(), chipText); 1085a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 10863b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 1087054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 108805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 108905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 10908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10918684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 10928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 10938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 1097b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1098b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1099b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1100b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 11082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 11092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 11104031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 11114031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ Spannable getSpannable() { 1112ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1117b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1119b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1120b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1121b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1122b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 113249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (enoughToFilter() && !isCompletedToken(text)) { 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 114649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 114749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /*package*/ boolean isCompletedToken(CharSequence text) { 114849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (TextUtils.isEmpty(text)) { 114949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 115049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Check to see if this is a completed token before filtering. 115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int end = text.length(); 115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int start = mTokenizer.findTokenStart(text, end); 115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String token = text.toString().substring(start, end).trim(); 115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (!TextUtils.isEmpty(token)) { 115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = token.charAt(token.length() - 1); 115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON; 115849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 115949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 116049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 116149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 116736d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 11728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 11738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 11748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 11758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 11768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 11778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1180d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1181d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1182d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1183d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1185d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1186c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 11876caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 11900436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1195c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 12018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 12028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1205b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1206c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1207c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1208c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1209416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 12105753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 12115753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 12125753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1217c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1221416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1222416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1223c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1224416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1225416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1226416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1230c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1234e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 123577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1236416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1238368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1239e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1240e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1244b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1251b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1252b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1253b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1254e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1255e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1256e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1257e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1258e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1259e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1260b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1261b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1262b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1263b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1264b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 12651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 12661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 12671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 12681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 12691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1270a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1272b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1275b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1276e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1277b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1278b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1292c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1293c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1294c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12964fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 12974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 12984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 13014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1302c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1306c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 13104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 13114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 13124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 13144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1317c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1321b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1322b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1323b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1326c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1327c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13304031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 133100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to add to the list of addresses. 133200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createAddressText(RecipientEntry entry) { 13338659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 13348659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 13358659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 13368659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 13378659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1338a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1339a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1340a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1341c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 1342c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (tokenized != null && tokenized.length > 0) { 1343c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira address = tokenized[0].getAddress(); 1344c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 1345a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 13468659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 134700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String trimmedDisplayText = token.toString().trim(); 13488659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 13498659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 135000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .terminateToken(trimmedDisplayText) : trimmedDisplayText; 135100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 135200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 135300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 135400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to display in a chip. 135500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createChipDisplayText(RecipientEntry entry) { 135600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String display = entry.getDisplayName(); 135700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String address = entry.getDestination(); 135800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 135900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira display = null; 136000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 136100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (address != null) { 136200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Tokenize out the address in case the address already 136300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // contained the username as well. 136400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 136500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (tokenized != null && tokenized.length > 0) { 136600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira address = tokenized[0].getAddress(); 136700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 136800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 136900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (!TextUtils.isEmpty(display)) { 137000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return display; 137100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else if (!TextUtils.isEmpty(address)){ 137200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return address; 137300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else { 137400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return new Rfc822Token(display, address, null).toString(); 137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 13768659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 13778659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1378fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 137900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String displayText = createAddressText(entry); 1380a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (TextUtils.isEmpty(displayText)) { 1381a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira return null; 1382a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1384b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = displayText.length()-1; 1385c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1386c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 13896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 13906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 13926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 14038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 14048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1406c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1408c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1409c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 14110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 14120fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 14131e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 14141e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 14151e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1419c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1420c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 14234221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 14244221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 14254221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 14264221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1427f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1428c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1429c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 14310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 14320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 14330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 14350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 14360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 14370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 14391e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira if (RecipientEntry.isCreatedRecipient(item.getContactId()) 14401e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira && (TextUtils.isEmpty(item.getDisplayName()) 14411e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 14421e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira || (mValidator != null && !mValidator.isValid(destination)))) { 14430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 14440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 14450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 14460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 14480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1452c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 145300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 14547a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14557a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14567a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 14577a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1459c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1460c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1461c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 146200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 146300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 146400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */ Collection<Long> getDataIds() { 146500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira final Set<Long> result = new HashSet<Long>(); 146600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip [] chips = getSortedRecipients(); 146700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (chips != null) { 146800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira for (RecipientChip chip : chips) { 146900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira result.add(chip.getDataId()); 147000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 147100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 147200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return result; 147383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 147483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 14754031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 147600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */RecipientChip[] getSortedRecipients() { 147700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] recips = getSpannable() 147800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .getSpans(0, getText().length(), RecipientChip.class); 14796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 148000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .asList(recips)); 14816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 14826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 14836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 14846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 14856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 14866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 14876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 14886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 14896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 14916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 14926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 14946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 14976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 14986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 15004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 15024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 15074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 15114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 15158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 15168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 15174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 15194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1522a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1523a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ImageSpan getMoreChip() { 152422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 152522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 152622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 152722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 152822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 15298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1530045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1531045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1532045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 15338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1534a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1535a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void createMoreChip() { 1536bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1537bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1538bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1539bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 15406f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 15416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 15426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 15436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 154583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 15460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 155083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 15514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1552c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1553c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1554c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1555c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1556c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1557c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 15584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 15594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 15604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 156122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira int adjustedHeight = height; 156222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira Layout layout = getLayout(); 156322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (layout != null) { 156422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira adjustedHeight -= layout.getLineDescent(0); 156522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 156622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 15674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 15694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 15706f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 15714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1572368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 15734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 15740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 15750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15774e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1578368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 15794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 15804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 15816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1582368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1583368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 15849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1585368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 15869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1587368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1588368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 15899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 15906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 15916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 15926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 15936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 159477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1595368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 15964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 159777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 159877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 159977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 16004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 160177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 16020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 16034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 16058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 16068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 16078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 16088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1609a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1610a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /*package*/ void removeMoreChip() { 16114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 16124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 16134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 16144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 16154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 16164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 16174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 1618c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 161964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 162064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 1621c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (recipients == null || recipients.length == 0) { 1622c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira return; 1623c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 162464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 16254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 16264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 16276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 16280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 16290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 16306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 16316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 163264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 163364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 163464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 163564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 163664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 163764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1638bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1639bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1640bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1641bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1642bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 16434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 16454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1649c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1653b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1655b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1656b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1657b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1658b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1659b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1660c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 166100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip selectChip(RecipientChip currentChip) { 16621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 16641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 16651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 16661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 16671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 16692f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1671b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1672b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1673b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1675b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1676b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1680b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1681fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1682b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 168383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1684b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 168583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 16868b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 168783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1688b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 16891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1691c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 16921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 16936ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1694b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1695b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 16961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 16981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 16991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 17001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 17011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 17021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1706b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 17111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 17151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 17161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 17186ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1720fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1722fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1723c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 17241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 17251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 17261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 17271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 17281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 17291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1730e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 17311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 17351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 17391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 17401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 17421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 17431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 17441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 17451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 17461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1752b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 175300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private void unselectChip(RecipientChip chip) { 1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1757c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 17595753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 17605753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 17615753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 17625753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1764c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1765b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17668b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 17678b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 17688b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 17698b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17708b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 17718b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 17728b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1773c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1774b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1775b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1780c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1785b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1786b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 17953656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1796b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1797b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1798b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1799ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 1800ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /*pacakge*/ void removeChip(RecipientChip chip) { 1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1802b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1804b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1805b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int toDelete = spanEnd; 1806b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1807b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1808b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1809b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1811b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira // Always remove trailing spaces when removing a chip. 1812b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1813b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira toDelete++; 1814b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1815b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1816b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira text.delete(spanStart, toDelete); 1817b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1819c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1822b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1823b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1824b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 182600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 182700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) { 1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1829b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1830b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1831c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1832b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1833b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1834b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1835b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1836b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1837a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1838a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (start == -1 || end == -1) { 1839a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1840a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.insert(0, chipText); 1841a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } else { 1842a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (!TextUtils.isEmpty(chipText)) { 1843a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // There may be a space to replace with this chip's new 1844a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // associated 1845a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // space. Check for it 1846a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira int toReplace = end; 1847a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1848a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira && editable.charAt(toReplace) == ' ') { 1849a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira toReplace++; 1850a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1851a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, toReplace, chipText); 1852b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1853b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1854c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1855b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1857b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1858c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1860c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1863b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1865b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1866b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1867b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1868b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1870b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1871b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1872c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1873c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1874b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1875c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1876368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1877368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1878368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1879368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1880311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1881311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1882311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1883311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1884311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1885311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1886e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 18891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 18901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 18911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 18921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 18931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 18941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 18951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 18961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 18971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 18981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 19001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 19011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 19031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 19051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1906368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 19095753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 19105753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 19115753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1918054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1920054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 192176ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1922054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1923054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1924054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1925054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1926054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1927054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1928e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1929e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1930e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1931e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1932e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1936e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 19373b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1939e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1940e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1941e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1942e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1943e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 19461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1949e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1950e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 195122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 1952e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1953e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 195477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 19553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 19563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}. 19573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 19583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private void handlePasteClip(ClipData clip) { 19593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham removeTextChangedListener(mTextWatcher); 19603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 19613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){ 19623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham for (int i = 0; i < clip.getItemCount(); i++) { 19633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham CharSequence paste = clip.getItemAt(i).getText(); 19643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (paste != null) { 19653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham int start = getSelectionStart(); 19663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham int end = getSelectionEnd(); 19673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham Editable editable = getText(); 19683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (start >= 0 && end >= 0 && start != end) { 19693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham editable.append(paste, start, end); 19703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } else { 19713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham editable.insert(end, paste); 19723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteAndReplace(); 19743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19773e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 19783e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mHandler.post(mAddTextWatcher); 19793e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 198149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira @Override 198249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira public boolean onTextContextMenuItem(int id) { 198349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (id == android.R.id.paste) { 198449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 198549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Context.CLIPBOARD_SERVICE); 19863e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteClip(clipboard.getPrimaryClip()); 198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return true; 198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return super.onTextContextMenuItem(id); 199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 199149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 19921e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira private void handlePasteAndReplace() { 19931e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira ArrayList<RecipientChip> created = handlePaste(); 19941e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira if (created != null && created.size() > 0) { 19951e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira // Perform reverse lookups on the pasted contacts. 19961e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira IndividualReplacementTask replace = new IndividualReplacementTask(); 19971e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira replace.execute(created); 19981e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira } 19991e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira } 20001e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira 200149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 20021e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira /* package */ArrayList<RecipientChip> handlePaste() { 200349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String text = getText().toString(); 200449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 200549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String lastAddress = text.substring(originalTokenStart); 200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenStart = originalTokenStart; 200749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int prevTokenStart = tokenStart; 200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip findChip = null; 20091e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira ArrayList<RecipientChip> created = new ArrayList<RecipientChip>(); 201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != 0) { 201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // There are things before this! 201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart != 0 && findChip == null) { 201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira prevTokenStart = tokenStart; 201449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = mTokenizer.findTokenStart(text, tokenStart); 201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira findChip = findChip(tokenStart); 201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != originalTokenStart) { 201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (findChip != null) { 201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = prevTokenStart; 202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenEnd; 202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip createdChip; 202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart < originalTokenStart) { 202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart)); 202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira commitChip(tokenStart, tokenEnd, getText()); 202649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira createdChip = findChip(tokenStart); 202749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // +1 for the space at the end. 202849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = getSpannable().getSpanEnd(createdChip) + 1; 20291e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira created.add(createdChip); 203049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Take a look at the last token. If the token has been completed with a 203449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // commit character, create a chip. 203549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (isCompletedToken(lastAddress)) { 203649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Editable editable = getText(); 20371e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart); 20381e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira commitChip(tokenStart, editable.length(), editable); 20391e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira created.add(findChip(tokenStart)); 204049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 20411e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira return created; 204249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 204349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 204449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 204549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /* package */int movePastTerminators(int tokenEnd) { 204649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd >= length()) { 204749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 204849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 204949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = getText().toString().charAt(tokenEnd); 205049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) { 205149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 205249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 205349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // This token had not only an end token character, but also a space 205449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // separating it from the next token. 205549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') { 205649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 205749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 205849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 205949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 206049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 206177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 206277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 206377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 206477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 206577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 207177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 207477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 207577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 20806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 208577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 208677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 208777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 208877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 208900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 209077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 209177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 209477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 209577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 20961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 209777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 209877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 20991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 21001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 210177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 210477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 210577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 210677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 211577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 2120b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 21212e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira oldText.removeSpan(chip); 2122b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 21236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 21246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 2125b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 21266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 212777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 212877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 212977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 21312e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira setText(text); 213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 213477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 213677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 213977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 214077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 214877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 214977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 215000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 215177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 215277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 215377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 215477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 21551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 215677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 215777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 21581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 21591e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase())); 216077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 216177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 216277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 216377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 216477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 216577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 216677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 216777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 216877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 216977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 217177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 2173b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 21746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 21756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 21766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 21776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 21786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 21796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 21806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 21816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 21826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 2185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2186b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 2187b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 22063e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (mDragEnabled) { 22073e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Start drag-and-drop for the selected chip. 22083e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham startDrag(currentChip); 22093e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } else { 22103e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Copy the selected chip email address. 22113e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham showCopyDialog(currentChip.getEntry().getDestination()); 22123e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22133e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22143e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22153e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22163e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22173e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Enables drag-and-drop for chips. 22183e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22193e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void enableDrag() { 22203e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mDragEnabled = true; 22213e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22223e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22233e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22243e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Starts drag-and-drop for the selected chip. 22253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private void startDrag(RecipientChip currentChip) { 22273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham String address = currentChip.getEntry().getDestination(); 22283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA); 22293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Start drag mode. 22313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham startDrag(data, new RecipientChipShadow(currentChip), null, 0); 22323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Remove the current chip, so drag-and-drop will result in a move. 22343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // TODO (phamm): consider readd this chip if it's dropped outside a target. 22353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham removeChip(currentChip); 22363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Handles drag event. 22403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 22423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public boolean onDragEvent(DragEvent event) { 22433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham switch (event.getAction()) { 22443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DRAG_STARTED: 22453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Only handle plain text drag and drop. 22463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 22473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DRAG_ENTERED: 22483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham requestFocus(); 22493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return true; 22503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DROP: 22513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteClip(event.getClipData()); 22523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return true; 22533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return false; 22553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Drag shadow for a {@link RecipientChip}. 22593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private final class RecipientChipShadow extends DragShadowBuilder { 22613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private final RecipientChip mChip; 22623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public RecipientChipShadow(RecipientChip chip) { 22643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mChip = chip; 22653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 22683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { 22693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham Rect rect = mChip.getDrawable().getBounds(); 22703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham shadowSize.set(rect.width(), rect.height()); 22713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham shadowTouchPoint.set(rect.centerX(), rect.centerY()); 22723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 22753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void onDrawShadow(Canvas canvas) { 22763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mChip.getDrawable().draw(canvas); 2277b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2278b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2279b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2280b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 2281b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 2282b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 2283b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 2284b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 2285b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 2286b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 2287b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 2288b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 2289b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2290b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2291b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2292b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 2293b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2294b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2295b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2296b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2297b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2298b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 2299b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2300b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2301b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2302b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2303b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 2304b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2305b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2306b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2307b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2308b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2309b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 2310b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 2311b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2312b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2313b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2314b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 2315b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 2316b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 2317b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2318b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2319b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2320b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 232177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2322