RecipientEditTextView.java revision 6fe9af5b1cc380243f0fad9354ab7bd41ac2983b
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; 412e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira float[] widths = new float[1]; 413e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira paint.getTextWidths(" ", widths); 41400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 415e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira calculateAvailableWidth(true) - deleteWidth - widths[0]); 4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 4241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 4251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 4281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 42977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 43097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 43197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 43297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 4331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 434f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 435f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 436f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 437f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 438f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 439f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 447045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 448e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 450c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 452e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 454e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira float[] widths = new float[1]; 455e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira paint.getTextWidths(" ", widths); 45600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 457e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira calculateAvailableWidth(false) - iconWidth - widths[0]); 4581a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4591a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 463c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 467045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 468045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 469045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 470045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4749024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 47690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 47790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 47890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 47990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 48090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 48190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 48290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 48390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4859024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4869024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4879024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4889024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 492ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira if (photo != null) { 493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Rect backgroundPadding = new Rect(); 495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground.getPadding(backgroundPadding); 496ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 0 + backgroundPadding.top, 498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira width - backgroundPadding.right, 499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira height - backgroundPadding.bottom); 500ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Matrix matrix = new Matrix(); 501ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 502ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5059024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 5069024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 508e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 50997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 510379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 51197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 5151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 51800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** 51900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira * Get the background drawable for a RecipientChip. 52000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira */ 52100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 52200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ Drawable getChipBackground(RecipientEntry contact) { 52300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 52400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mChipBackground : mInvalidChipBackground; 52500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 52600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 52797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 52897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 5293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham paint.getTextBounds(text, 0, text.length(), bounds); 53097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 53197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 53297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 53397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 53400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 5361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 5371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 539c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 5401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 541c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 54477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 548e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 549c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 551045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 55777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 56077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 561c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5622d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5632d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 565045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 566045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5675519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5685519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 570c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5715519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 572416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 57397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 57497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 575f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 576f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 582c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 586c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5874f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5884f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5894f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5901e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5914f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5941426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5961426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 597c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 600c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 60143876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 602045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 603d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 610b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 611b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 612b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 613b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 614b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 615b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 616b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 617b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 618a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 619a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void setMoreItem(TextView moreItem) { 620a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mMoreItem = moreItem; 621a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 622a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipBackground(Drawable chipBackground) { 626ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground = chipBackground; 627ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 628ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 629ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 630ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipHeight(int height) { 631ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipHeight = height; 632ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 633ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 637bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 638bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 639bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 640bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 641bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 642bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 643bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 644bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 645c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 646c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 647c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 64853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (width != 0 && height != 0) { 64953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount > 0) { 65053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira postHandlePendingChips(); 65153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } else { 65253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira checkChipWidths(); 65353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 6547bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 65577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 65600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (mScrollView == null && !mTriedGettingScrollView) { 657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 658416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 659416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 660416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 661416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 662416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 663416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 66400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mTriedGettingScrollView = true; 665416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 666c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 667c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 668a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 669a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 670a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 671a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 672a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void checkChipWidths() { 67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Check the widths of the associated chips. 67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (chips != null) { 67753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira Rect bounds; 67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira for (RecipientChip chip : chips) { 67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira bounds = chip.getDrawable().getBounds(); 68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) { 68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Need to redraw that chip. 68253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira replaceChip(chip, chip.getEntry()); 68353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 68453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 685a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 68653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 68753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void handlePendingChips() { 6897bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6917bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6927bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6937bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6947bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 69553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 69653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount <= 0) { 69753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira return; 69853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 69953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 709a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 710a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 711a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 714a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 7150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 7170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 71800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira sanitizeEnd(); 71922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 721a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 722a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 725a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 726a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 727a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 728a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 72977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 730a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 732a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 733a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 73422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 735a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 73677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 73777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 738a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 739a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 7450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 74600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 74700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void sanitizeEnd() { 7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 74900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 75322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 75700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira lastSpan = getLastChip(); 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 7750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 7760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 7781e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 7791e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7811e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7821e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 783b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 7850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 7860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 7870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 79000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String destText = createAddressText(entry); 791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 792b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = destText.length() - 1; 793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 798d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 799d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 800d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 801d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 803d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 804d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 805d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 80622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 80722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 80822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 8096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 810d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 811d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 81277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 8130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 8150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 816d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 817d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 818d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8190fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 8201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 8216ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 822454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 823454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 8241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 8251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 8261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 8276ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 8286ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 8296ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 8306ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 8316ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 8321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 8356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 8376ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 8386ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 845a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 846a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 847a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 848a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 849a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 850a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 851a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 852a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 853490556a764a879cd0eaff358e90705cc1335c92eErik } 854d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 856454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 857a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 858a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 8591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 8616ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 8626ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 8636ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 8646ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 8651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 8661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 8671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 8681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 8690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 8710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 873c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 874c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 875c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 876c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 877c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 878c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 8838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 891c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 892c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 893c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 898c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 89995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 90095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 90195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 90295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 90395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 90495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 90595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 9068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 9088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 9098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 9108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 9118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 9128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 9138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 91495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 916c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 917c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 918c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 919c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 920d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 922c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 926e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 927e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 93095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 934e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 935e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 936e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 937e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 939e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 942c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 944c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 945c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 947e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 948e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 949e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 950e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 951e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 952e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 953e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 954e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 955045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 957045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 958045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 959045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 960045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 961045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 9628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 9634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 964045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 9654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 966dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 9744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 983e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 985e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 9864e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 987054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 9894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 99149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ListAdapter adapter = getAdapter(); 99249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (adapter != null && adapter.getCount() > 0 && enoughToFilter() 99349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira && end == getSelectionEnd()) { 994e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 995e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 996e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 997e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 998e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 100049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (editable.length() > tokenEnd + 1) { 100149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char charAt = editable.charAt(tokenEnd + 1); 100249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) { 100349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 100449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1005a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 1006b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 1007e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 1008e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 10091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 1010d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 1011d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 1012d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 101371fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira if (chipText != null && start > -1 && end > -1) { 10145ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 10155ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 1016d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 101749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Only dismiss the dropdown if it is related to the text we 101849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // just committed. 101949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // For paste, it may not be as there are possibly multiple 102049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // tokens being added. 102149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (end == getSelectionEnd()) { 102249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira dismissDropDown(); 102349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1024f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 10254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 1026d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1027d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 10284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 1029d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1030d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 10314031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10324031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ void sanitizeBetween() { 1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 10344031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira RecipientChip[] recips = getSortedRecipients(); 1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 1037f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 1038f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 1039f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 1040f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1041f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 1042f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 1043f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 1044f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1045c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira Editable text = getText(); 10464031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1047c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira // There is nothing after this chip. 1048c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira return; 1049c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira } 1050c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira if (text.charAt(startLooking) == ' ') { 1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 1052f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1053f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1054a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira if (startLooking >= 0 && end >= 0 && startLooking != end) { 1055f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 1056f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1057f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1058f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1059f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 1060e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 10621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 10631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 10641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 10651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 10661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 10671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 106805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 10691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 1070e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1071e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1072e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 107339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 107439f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 107539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 107639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 107739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 1078e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 1079e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 1080e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 1081e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 1082e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 10833b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 10843b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 10853b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 10863b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 10876fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira int selEnd = getSelectionEnd(); 10886fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira if (chipText != null && start > -1 && selEnd > -1) { 10896fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira editable.replace(start, selEnd, chipText); 1090a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 10913b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 1092054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 109305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 109405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 10958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 10978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 10988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 1101b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 1102b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1103b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1104b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1105b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 11132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 11142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 11154031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 11164031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ Spannable getSpannable() { 1117ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1120b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1121b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1122b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1126b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1127b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 113749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (enoughToFilter() && !isCompletedToken(text)) { 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1142c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1144c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /*package*/ boolean isCompletedToken(CharSequence text) { 115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (TextUtils.isEmpty(text)) { 115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Check to see if this is a completed token before filtering. 115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int end = text.length(); 115849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int start = mTokenizer.findTokenStart(text, end); 115949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String token = text.toString().substring(start, end).trim(); 116049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (!TextUtils.isEmpty(token)) { 116149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = token.charAt(token.length() - 1); 116249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON; 116349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 116449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 116549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 116649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 117236d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 11778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 11788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 11798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 11808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 11818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 11828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1183c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1185d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1186d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1187d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1188d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1190d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 11926caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 11950436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1197c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1198c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1199c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1201c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 12068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 12078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1214416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 12155753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 12165753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 12175753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1224c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1225c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1226416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1227416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1228c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1229416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1230416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1231416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1234b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1235c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1236b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1239e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 124077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1241416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1243368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1244e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1245e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1251b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1252b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1253b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1254b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1255b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1258b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1259e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1260e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1261e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1262e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1263e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1264e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1265b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1266b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 12701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 12711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 12721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 12731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 12741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1275a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1276b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1277b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1278b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1279b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1280b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1281e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1282b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1283b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1292c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1293c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1294c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1297c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1298c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13014fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 13024fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 13034fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 13064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1307c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1311c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 13154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 13164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 13174fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13184fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 13194fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13204fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1322c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1326b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1327b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1328b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1330c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1334c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13354031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 133600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to add to the list of addresses. 133700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createAddressText(RecipientEntry entry) { 13388659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 13398659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 13408659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 13418659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 13428659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1343a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1344a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1345a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1346c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 1347c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (tokenized != null && tokenized.length > 0) { 1348c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira address = tokenized[0].getAddress(); 1349c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 1350a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 13518659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 135200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String trimmedDisplayText = token.toString().trim(); 13538659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 13548659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 135500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .terminateToken(trimmedDisplayText) : trimmedDisplayText; 135600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 135700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 135800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 135900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to display in a chip. 136000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createChipDisplayText(RecipientEntry entry) { 136100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String display = entry.getDisplayName(); 136200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String address = entry.getDestination(); 136300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 136400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira display = null; 136500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 136600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (address != null) { 136700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Tokenize out the address in case the address already 136800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // contained the username as well. 136900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 137000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (tokenized != null && tokenized.length > 0) { 137100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira address = tokenized[0].getAddress(); 137200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 137300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 137400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (!TextUtils.isEmpty(display)) { 137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return display; 137600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else if (!TextUtils.isEmpty(address)){ 137700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return address; 137800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else { 137900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return new Rfc822Token(display, address, null).toString(); 138000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 13818659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 13828659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1383fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 138400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String displayText = createAddressText(entry); 1385a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (TextUtils.isEmpty(displayText)) { 1386a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira return null; 1387a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1389b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = displayText.length()-1; 1390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 13946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 13956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 13976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1399c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1400c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1402c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 14088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 14098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 14160fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 14170fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 14181e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 14191e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 14201e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1421c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1422c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1423c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1426c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 14284221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 14294221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 14304221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 14314221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1432f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 14360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 14370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 14400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 14410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 14420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 14430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 14441e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira if (RecipientEntry.isCreatedRecipient(item.getContactId()) 14451e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira && (TextUtils.isEmpty(item.getDisplayName()) 14461e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 14471e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira || (mValidator != null && !mValidator.isValid(destination)))) { 14480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 14490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 14500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 14510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 14530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 145800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 14597a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14607a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14617a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 14627a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1463c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1465c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1466c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 146700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 146800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 146900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */ Collection<Long> getDataIds() { 147000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira final Set<Long> result = new HashSet<Long>(); 147100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip [] chips = getSortedRecipients(); 147200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (chips != null) { 147300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira for (RecipientChip chip : chips) { 147400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira result.add(chip.getDataId()); 147500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 147600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 147700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return result; 147883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 147983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 14804031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 148100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */RecipientChip[] getSortedRecipients() { 148200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] recips = getSpannable() 148300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .getSpans(0, getText().length(), RecipientChip.class); 14846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 148500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .asList(recips)); 14866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 14876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 14886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 14896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 14916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 14926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 14946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 14956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 14966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 14976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 14986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 14996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 15026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 15036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 15054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 15074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 15164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 15208684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 15218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 15224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 15244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1527a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1528a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ImageSpan getMoreChip() { 152922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 153022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 153122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 153222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 153322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 15348684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1535045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1536045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1537045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 15388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1539a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1540a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void createMoreChip() { 1541bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1542bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1543bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1544bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 15456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 15466f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 15476f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 15486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 155083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 15510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 155583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 15564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1557c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1558c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1559c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1560c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1561c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1562c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 15634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 15644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 15654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 156622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira int adjustedHeight = height; 156722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira Layout layout = getLayout(); 156822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (layout != null) { 156922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira adjustedHeight -= layout.getLineDescent(0); 157022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 157122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 15724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 15744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 15756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 15764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1577368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 15784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 15790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 15800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1583368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 15844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 15854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 15866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1587368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1588368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 15899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1590368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 15919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1592368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1593368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 15949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 15956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 15966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 15976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 15986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 159977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1600368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 16014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 160277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 160377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 160477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 16054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 160677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 16070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 16084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 16108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 16118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 16128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 16138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1615a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /*package*/ void removeMoreChip() { 16164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 16174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 16184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 16194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 16204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 16214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 16224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 1623c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 162464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 162564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 1626c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (recipients == null || recipients.length == 0) { 1627c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira return; 1628c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 162964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 16304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 16314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 16326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 16330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 16340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 16356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 16366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 163764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 163864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 163964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 164064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 164164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 164264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1643bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1644bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1645bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1646bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1647bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 16484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 16504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1654c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1655b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1656b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1657b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1658b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1659b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1660b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1661b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1662b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1663b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1664b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1665c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 166600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip selectChip(RecipientChip currentChip) { 16671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 16681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 16691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 16701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 16711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 16721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 16731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 16742f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1676b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1680b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1681b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1682b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1684b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1685b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1686fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1687b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 168883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1689b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 169083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 16918b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 169283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1693b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 16941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1696c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 16986ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1699b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1700b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 17011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 17021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 17061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1711b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 17151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 17161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 17201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 17211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 17236ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 17241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1725fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1727fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1728c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 17291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 17301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 17311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1735e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 17391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 17401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 17411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 17421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 17431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 17441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 17451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 17471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 17481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 17491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 17501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 17511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1755b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1756b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1757b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 175800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private void unselectChip(RecipientChip chip) { 1759b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1761b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1762c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1763b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 17645753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 17655753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 17665753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 17675753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1768b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1769c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17718b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 17728b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 17738b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 17748b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17758b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 17768b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 17778b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1778c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1783c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1785c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1786b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1790b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1794b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1795b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1796b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1797b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1798b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1799b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 18003656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1802b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1804ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 1805ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /*pacakge*/ void removeChip(RecipientChip chip) { 1806b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1807b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1808b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1809b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1810b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int toDelete = spanEnd; 1811b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1814b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1815c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1816b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira // Always remove trailing spaces when removing a chip. 1817b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1818b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira toDelete++; 1819b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1821b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira text.delete(spanStart, toDelete); 1822b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1823b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1824c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1827b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1829b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1830b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 183100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 183200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) { 1833b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1834b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1835b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1837b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1838b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1839b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1840b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1841b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1842a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1843a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (start == -1 || end == -1) { 1844a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1845a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.insert(0, chipText); 1846a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } else { 1847a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (!TextUtils.isEmpty(chipText)) { 1848a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // There may be a space to replace with this chip's new 1849a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // associated 1850a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // space. Check for it 1851a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira int toReplace = end; 1852a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1853a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira && editable.charAt(toReplace) == ' ') { 1854a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira toReplace++; 1855a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1856a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, toReplace, chipText); 1857b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1858b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1859c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1860b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1863c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1866b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1867b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1868b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1870b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1871b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1872b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1873b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1874b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1875b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1876b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1877c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1878c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1879b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1880c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1881368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1882368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1883368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1884368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1885311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1886311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1887311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1888311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1889311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1890311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 18941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 18951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 18961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 18971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 18981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 18991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 19001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 19011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 19021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 19031e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 19051e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 19061e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19071e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 19081e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 19101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1911368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 19145753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 19155753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 19165753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1921e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1922e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1923054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1924e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1925054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 192676ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1927054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1928054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1929054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1930054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1931054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1932054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1936e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1937e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1939e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1940e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1941e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 19423b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1943e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1946e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1949e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1950e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 19511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1952e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1953e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1954e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1955e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 195622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 1957e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1958e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 195977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 19603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 19613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}. 19623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 19633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private void handlePasteClip(ClipData clip) { 19643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham removeTextChangedListener(mTextWatcher); 19653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 19663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){ 19673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham for (int i = 0; i < clip.getItemCount(); i++) { 19683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham CharSequence paste = clip.getItemAt(i).getText(); 19693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (paste != null) { 19703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham int start = getSelectionStart(); 19713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham int end = getSelectionEnd(); 19723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham Editable editable = getText(); 19733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (start >= 0 && end >= 0 && start != end) { 19743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham editable.append(paste, start, end); 19753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } else { 19763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham editable.insert(end, paste); 19773e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19783e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteAndReplace(); 19793e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19813e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19823e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 19833e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mHandler.post(mAddTextWatcher); 19843e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 19853e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 198649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira @Override 198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira public boolean onTextContextMenuItem(int id) { 198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (id == android.R.id.paste) { 198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Context.CLIPBOARD_SERVICE); 19913e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteClip(clipboard.getPrimaryClip()); 199249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return true; 199349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 199449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return super.onTextContextMenuItem(id); 199549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 199649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 19971e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira private void handlePasteAndReplace() { 19981e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira ArrayList<RecipientChip> created = handlePaste(); 19991e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira if (created != null && created.size() > 0) { 20001e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira // Perform reverse lookups on the pasted contacts. 20011e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira IndividualReplacementTask replace = new IndividualReplacementTask(); 20021e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira replace.execute(created); 20031e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira } 20041e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira } 20051e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira 200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 20071e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira /* package */ArrayList<RecipientChip> handlePaste() { 200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String text = getText().toString(); 200949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String lastAddress = text.substring(originalTokenStart); 201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenStart = originalTokenStart; 201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int prevTokenStart = tokenStart; 201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip findChip = null; 20141e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira ArrayList<RecipientChip> created = new ArrayList<RecipientChip>(); 201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != 0) { 201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // There are things before this! 201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart != 0 && findChip == null) { 201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira prevTokenStart = tokenStart; 201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = mTokenizer.findTokenStart(text, tokenStart); 202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira findChip = findChip(tokenStart); 202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != originalTokenStart) { 202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (findChip != null) { 202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = prevTokenStart; 202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 202649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenEnd; 202749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip createdChip; 202849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart < originalTokenStart) { 202949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart)); 203049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira commitChip(tokenStart, tokenEnd, getText()); 203149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira createdChip = findChip(tokenStart); 203249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // +1 for the space at the end. 203349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = getSpannable().getSpanEnd(createdChip) + 1; 20341e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira created.add(createdChip); 203549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Take a look at the last token. If the token has been completed with a 203949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // commit character, create a chip. 204049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (isCompletedToken(lastAddress)) { 204149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Editable editable = getText(); 20421e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart); 20431e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira commitChip(tokenStart, editable.length(), editable); 20441e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira created.add(findChip(tokenStart)); 204549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 20461e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira return created; 204749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 204849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 204949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 205049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /* package */int movePastTerminators(int tokenEnd) { 205149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd >= length()) { 205249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 205349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 205449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = getText().toString().charAt(tokenEnd); 205549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) { 205649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 205749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 205849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // This token had not only an end token character, but also a space 205949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // separating it from the next token. 206049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') { 206149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 206249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 206349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 206449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 206549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 207177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 207477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 207577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 208077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 20856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 208677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 208777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 208877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 208977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 209077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 209177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 209400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 209577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 209677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 209777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 209877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 209977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 210077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 21011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 21041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 21051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 210677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 211577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 212077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 212177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 212277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 212377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 212477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 2125b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 21262e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira oldText.removeSpan(chip); 2127b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 21286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 21296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 2130b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 21316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 213477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 21362e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira setText(text); 213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 213877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 213977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 214877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 214977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 215077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 215177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 215277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 215377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 215477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 215500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 215677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 215777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 215877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 215977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 21601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 216177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 216277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 21631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 21641e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase())); 216577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 216677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 216777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 216877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 216977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 217077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 217277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 217677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 2178b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 21796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 21806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 21816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 21826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 21836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 21846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 21856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 21866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 21876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2206b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 2207b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 2208b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 2209b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 2210b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 22113e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (mDragEnabled) { 22123e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Start drag-and-drop for the selected chip. 22133e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham startDrag(currentChip); 22143e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } else { 22153e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Copy the selected chip email address. 22163e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham showCopyDialog(currentChip.getEntry().getDestination()); 22173e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22183e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22193e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22203e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22213e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22223e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Enables drag-and-drop for chips. 22233e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22243e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void enableDrag() { 22253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mDragEnabled = true; 22263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Starts drag-and-drop for the selected chip. 22303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private void startDrag(RecipientChip currentChip) { 22323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham String address = currentChip.getEntry().getDestination(); 22333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA); 22343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Start drag mode. 22363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham startDrag(data, new RecipientChipShadow(currentChip), null, 0); 22373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Remove the current chip, so drag-and-drop will result in a move. 22393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // TODO (phamm): consider readd this chip if it's dropped outside a target. 22403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham removeChip(currentChip); 22413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Handles drag event. 22453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 22473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public boolean onDragEvent(DragEvent event) { 22483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham switch (event.getAction()) { 22493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DRAG_STARTED: 22503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Only handle plain text drag and drop. 22513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 22523e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DRAG_ENTERED: 22533e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham requestFocus(); 22543e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return true; 22553e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DROP: 22563e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteClip(event.getClipData()); 22573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return true; 22583e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22593e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return false; 22603e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22613e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22623e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22633e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Drag shadow for a {@link RecipientChip}. 22643e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22653e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private final class RecipientChipShadow extends DragShadowBuilder { 22663e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private final RecipientChip mChip; 22673e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22683e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public RecipientChipShadow(RecipientChip chip) { 22693e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mChip = chip; 22703e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22713e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22723e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 22733e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { 22743e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham Rect rect = mChip.getDrawable().getBounds(); 22753e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham shadowSize.set(rect.width(), rect.height()); 22763e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham shadowTouchPoint.set(rect.centerX(), rect.centerY()); 22773e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22783e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22793e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 22803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void onDrawShadow(Canvas canvas) { 22813e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mChip.getDrawable().draw(canvas); 2282b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2283b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2284b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2285b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 2286b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 2287b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 2288b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 2289b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 2290b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 2291b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 2292b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 2293b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 2294b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2295b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2296b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2297b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 2298b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2299b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2300b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2301b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2302b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2303b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 2304b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2305b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2306b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2307b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2308b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 2309b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2310b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2311b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2312b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2313b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2314b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 2315b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 2316b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2317b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2318b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2319b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 2320b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 2321b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 2322b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2323b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2324b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2325b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 232677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2327