RecipientEditTextView.java revision 00be4ce28569ae7d1871a2f95453b0e6a6a47852
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; 30c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 32c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 3477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask; 35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler; 36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message; 3722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable; 382d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 39572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 47c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 522d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 53c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 544fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 55a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 56b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 57c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 58c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 62c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 63572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 64416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 66c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 67b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 69156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 71e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow; 72416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 73c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 74b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 75a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 77b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 8077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 88b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 89b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 90e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener, 91e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira PopupWindow.OnDismissListener { 92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 9400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 9500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 9600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 9700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 9800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 10200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 10300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final long DISMISS_DELAY = 300; 10400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 1054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 1064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 1074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 10800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static int sSelectedTextColor = -1; 10900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 11000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Resources for displaying chips. 111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 112c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 113c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 114c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Drawable mInvalidChipBackground; 11600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 11700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Drawable mChipBackgroundPressed; 11800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 11900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private float mChipHeight; 12000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 12100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private float mChipFontSize; 12200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Validator mValidator; 128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 137c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 139054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 140a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 141156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 142156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 143c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 144c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 15077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 154bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 166a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 169b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 17100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 172e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 173e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 17400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Obtain the enclosing scroll view, if it exists, so that the view can be 17500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // scrolled to show the last line of chips content. 176416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 177416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 17800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private boolean mTriedGettingScrollView; 179416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 186e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 187e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 188e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 189e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 19077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 19177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 197a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 198a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 199a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 200a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 201a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira private Runnable mDelayedShrink = new Runnable() { 202a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 203a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira @Override 204a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira public void run() { 205a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira shrink(); 206a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 207a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 208a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira }; 209a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 2102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 2112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 21277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 21377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 21477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 215b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 216e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAlternatesPopup.setOnDismissListener(this); 2171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 218e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAddressPopup.setOnDismissListener(this); 2190436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira mCopyDialog = new Dialog(context); 220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 221b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 222b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 224368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 225e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 226b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 229368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 235c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 237156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 238156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 239156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 240156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 242156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 243156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 244156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 245156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 246156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2486ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 249b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 250ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 251cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 25200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ RecipientChip getLastChip() { 25300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip last = null; 25400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 25500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (chips != null && chips.length > 0) { 25600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira last = chips[chips.length - 1]; 257ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 25800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return last; 2594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2604fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2634fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2644fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 26500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip last = getLastChip(); 26600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (last != null) { 26700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Grab the last chip and set the cursor to after it. 26800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length())); 269d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 270d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 271d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 272d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 27322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira @Override 27422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira public void onRestoreInstanceState(Parcelable state) { 27522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (!TextUtils.isEmpty(getText())) { 27622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(null); 27722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } else { 27822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(state); 27922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 28200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira @Override 2837c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira public Parcelable onSaveInstanceState() { 2847c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira // If the user changes orientation while they are editing, just roll back the selection. 2857c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira clearSelectedChip(); 2867c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira return super.onSaveInstanceState(); 2877c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira } 2887c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira 289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 291c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 292c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 294c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 295c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 296c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 297001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 298001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 299001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 300001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 3034f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 30405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 306c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 307c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 308a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 309c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 311a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 312a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 313a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 314a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 3156ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 316c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 317c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 318d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 319d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 320b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 321d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 3224e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 3234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 3244e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 325416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 3264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3272d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3282d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 329b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 330b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 331b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 332b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 333b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3355a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3365a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 339a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (getWidth() <= 0) { 340a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // We don't have the width yet which means the view hasn't been drawn yet 341a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // and there is no reason to attempt to commit chips yet. 342a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This focus lost must be the result of an orientation change 343a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // or an initial rendering. 344a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Re-post the shrink for later. 345a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.removeCallbacks(mDelayedShrink); 346a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.post(mDelayedShrink); 347a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira return; 348a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 349001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 350001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 351001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 352a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 353001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 354001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 355001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 356001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 357001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 359a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira Editable text = getText(); 360a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(text, start); 361a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This token was already tokenized, so skip past the ending token. 3628a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira if (whatEnd < text.length() && text.charAt(whatEnd) == ',') { 363a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira whatEnd++; 364a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 366001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 367a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int selEnd = getSelectionEnd(); 368a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (whatEnd != selEnd && whatEnd != editable.toString().trim().length()) { 369001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 370001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 371001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 372001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 373001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 374090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 375001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 38577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 38677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 38777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 38877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 38977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 39077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 394e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 395c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 396c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 397c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 398c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 399c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 402e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 406e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 40800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4111e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4151e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 4181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 4201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 42377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 42497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 42597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 42697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 4271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 428f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 429f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 430f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 431f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 432f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 433f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4361e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 440c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 441045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 442e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 446e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 44800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4501a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4511a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 457c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 459045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 460045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 461045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 462045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4649024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4669024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 46790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 46890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 46990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 47090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 47190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 47290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 47390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 47490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 47590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4769024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4779024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4789024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4809024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4829024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4839024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 484ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira if (photo != null) { 485ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 486ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Rect backgroundPadding = new Rect(); 487ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground.getPadding(backgroundPadding); 488ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 489ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 0 + backgroundPadding.top, 490ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira width - backgroundPadding.right, 491ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira height - backgroundPadding.bottom); 492ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Matrix matrix = new Matrix(); 493ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 494ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 495ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 496c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4979024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 4989024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 499c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 500e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 50197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 502379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 50397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 5061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 5071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 51000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** 51100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira * Get the background drawable for a RecipientChip. 51200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira */ 51300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 51400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ Drawable getChipBackground(RecipientEntry contact) { 51500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 51600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mChipBackground : mInvalidChipBackground; 51700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 51800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 51997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 52097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 52197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira paint.getTextBounds((String)text, 0, text.length(), bounds); 52297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 52397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 52497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 52597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 52600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 5271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 5281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 5291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 5301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 531c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 5321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 533c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5341e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5351e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 53677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5371e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 540e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 541c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 543045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5441e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 545c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 546c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 54977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 550c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 55277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5542d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5552d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 557045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 558045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5595519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5605519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 562c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5635519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 564416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 56597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 56697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 567f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 568f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 574c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5762d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5772d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 578c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5794f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5804f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5814f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5834f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5861426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5881426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 590b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 591b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 592c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 59343876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 594045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 595d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 596b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 597b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 598b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 599b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 600b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 601b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 602b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 605b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 606b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 607b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 608b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 610a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 611a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void setMoreItem(TextView moreItem) { 612a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mMoreItem = moreItem; 613a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 614a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 615ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 616ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 617ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipBackground(Drawable chipBackground) { 618ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground = chipBackground; 619ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 620ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 621ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 622ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipHeight(int height) { 623ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipHeight = height; 624ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 625ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 626bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 628bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 629bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 630bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 631bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 632bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 633bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 634bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 635bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 636bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 637c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 638c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 639c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 64053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (width != 0 && height != 0) { 64153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount > 0) { 64253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira postHandlePendingChips(); 64353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } else { 64453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira checkChipWidths(); 64553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 6467bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 64777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 64800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (mScrollView == null && !mTriedGettingScrollView) { 649416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 650416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 651416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 652416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 653416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 654416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 655416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 65600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mTriedGettingScrollView = true; 657416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 658c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 659c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 660a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 661a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 662a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 663a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 664a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 66553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void checkChipWidths() { 66653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Check the widths of the associated chips. 66753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 66853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (chips != null) { 66953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira Rect bounds; 67053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira for (RecipientChip chip : chips) { 67153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira bounds = chip.getDrawable().getBounds(); 67253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) { 67353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Need to redraw that chip. 67453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira replaceChip(chip, chip.getEntry()); 67553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 67653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 677a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void handlePendingChips() { 6817bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6837bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6847bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 68753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount <= 0) { 68953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira return; 69053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 692a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 693a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 694a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 695a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 696a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 697a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 698a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 699a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 700a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 701a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 702a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 703a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 704a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 7070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 708a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 7090fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 71000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira sanitizeEnd(); 71122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 712a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 713a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 714a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 715a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 716a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 717a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 718a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 719a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 720a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 72177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 722a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 723a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 724a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 725a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 72622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 727a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 72877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 72977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 730a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 7320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 7370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 73800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 73900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void sanitizeEnd() { 7400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 74100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 7420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 7430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 7440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 74522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 7470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 74900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira lastSpan = getLastChip(); 7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 7530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 7540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 7550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 7560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 7570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 7610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 7680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 7701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 7711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 775b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7761e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 7780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 781d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 78200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String destText = createAddressText(entry); 783d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 784b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = destText.length() - 1; 785d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 786d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 787d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 788d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 789d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 790d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 791d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 792d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 793d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 794d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 795d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 796d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 797d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 79822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 79922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 80022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 8016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 802d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 803d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 80477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 8050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 8070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 808d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 809d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 810d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 8121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 8136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 814454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 815454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 8161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 8171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 8181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 8196ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 8206ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 8216ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 8226ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 8236ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 8241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8266ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 8276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 828a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 8296ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 8306ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 831a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 832a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 833a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 834a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 835a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 836a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 837a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 838a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 839a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 840a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 841a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 842a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 843a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 844a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 845490556a764a879cd0eaff358e90705cc1335c92eErik } 846d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8476ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 848454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 849a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 850a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 8511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 8536ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 8546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 8566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 8571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 8581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 8591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 8601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 8610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 8630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 865c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 866c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 869c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 8758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8768684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8788684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 8798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 8808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 8818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 882c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 883c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 884c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 885c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 886c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 89195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 89295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 89395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 89495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 89595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 89695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 89795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 8988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 90695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 907c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 908c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 909c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 912d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 913c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 914c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 915e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 916e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 917e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 918e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 919e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 920e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 92295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 923e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 924e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 925e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 926e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 927e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 928e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 929e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 930e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 931e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 932e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 933e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 937c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 938e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 939e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 943e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 944e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 946e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 947045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 948045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 949045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 950045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 951045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 952045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 953045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 9548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 9554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 956045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 9574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 958dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 959e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 961e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 962e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 963e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 964e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 965e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 9664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 967e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 968e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 969e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 973e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 974e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 975e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 976e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 977e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 9784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 979054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 9814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 98349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ListAdapter adapter = getAdapter(); 98449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (adapter != null && adapter.getCount() > 0 && enoughToFilter() 98549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira && end == getSelectionEnd()) { 986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 989e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 991e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 99249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (editable.length() > tokenEnd + 1) { 99349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char charAt = editable.charAt(tokenEnd + 1); 99449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) { 99549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 99649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 997a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 998b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 1000e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 10011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 1002d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 1003d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 1004d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 100571fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira if (chipText != null && start > -1 && end > -1) { 10065ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 10075ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 1008d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 100949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Only dismiss the dropdown if it is related to the text we 101049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // just committed. 101149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // For paste, it may not be as there are possibly multiple 101249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // tokens being added. 101349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (end == getSelectionEnd()) { 101449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira dismissDropDown(); 101549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1016f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 10174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 1018d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1019d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 10204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 1021d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1022d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 10234031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10244031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ void sanitizeBetween() { 1025f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 10264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira RecipientChip[] recips = getSortedRecipients(); 1027f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 1028f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 1029f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 1030f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 1031f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 1032f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1033f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 1034f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 1035f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 1036f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1037c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira Editable text = getText(); 10384031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1039c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira // There is nothing after this chip. 1040c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira return; 1041c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira } 1042c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira if (text.charAt(startLooking) == ' ') { 1043f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 1044f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1045f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1046a63e3fa13ddf1370125b7b005775c538ec22b83aMindy Pereira if (startLooking >= 0 && end >= 0 && startLooking != end) { 1047f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 1048f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1049f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1050f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1051f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 1052e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 10531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 10541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 10551e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 10561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 10571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 10581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 10591e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 106005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 10611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 1062e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1063e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1064e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 106539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 106639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 106739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 106839f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 106939f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 1070e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 1071e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 1072e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 1073e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 1074e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 10753b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 10763b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 10773b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 10783b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 1079a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1080a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, getSelectionEnd(), chipText); 1081a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 10823b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 1083054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 108405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 108505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 10868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 10888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 10898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 1092b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 1093b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1094b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1095b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1096b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 11042d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 11052d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 11064031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 11074031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ Spannable getSpannable() { 1108ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1111b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1112b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1113b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1114b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1115b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1117b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1123c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1124c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1125c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 112849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (enoughToFilter() && !isCompletedToken(text)) { 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1141c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 114249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 114349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /*package*/ boolean isCompletedToken(CharSequence text) { 114449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (TextUtils.isEmpty(text)) { 114549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 114649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 114749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Check to see if this is a completed token before filtering. 114849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int end = text.length(); 114949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int start = mTokenizer.findTokenStart(text, end); 115049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String token = text.toString().substring(start, end).trim(); 115149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (!TextUtils.isEmpty(token)) { 115249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = token.charAt(token.length() - 1); 115349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON; 115449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 115549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 115649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 116336d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 11688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 11698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 11708684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 11718684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 11728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 11738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1176d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1177d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1178d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1179d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1181d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1182c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 11836caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 11860436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1195b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1196c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 11978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 11988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1205416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 12065753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 12075753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 12085753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1215c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1217416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1218416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1219c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1220416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1221416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1222416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1223b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1225b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1226c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1227b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1230e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 123177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1232416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1233b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1234368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1235e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1236e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1238b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1243b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1244b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1245b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1246b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1247b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1248b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1249b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1250e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1251e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1252e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1253e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1254e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1255e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1258b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1259b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1260b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 12611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 12621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 12631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 12641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 12651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1266a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1270b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1272e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1276c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1277c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1278c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1279c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1280c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1283c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1284c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1285c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1286c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1287c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1288c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1289c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1290c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1291c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12924fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 12934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 12944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1295c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1296c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 12974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1298c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1299c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1300c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1301c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13054fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 13064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 13074fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 13084fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 13104fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1313c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1317b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1318b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1319b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1321c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1322c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1323c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13264031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 132700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to add to the list of addresses. 132800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createAddressText(RecipientEntry entry) { 13298659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 13308659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 13318659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 13328659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 13338659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1334a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1335a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1336a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1337c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 1338c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (tokenized != null && tokenized.length > 0) { 1339c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira address = tokenized[0].getAddress(); 1340c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 1341a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 13428659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 134300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String trimmedDisplayText = token.toString().trim(); 13448659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 13458659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 134600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .terminateToken(trimmedDisplayText) : trimmedDisplayText; 134700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 134800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 134900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 135000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to display in a chip. 135100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createChipDisplayText(RecipientEntry entry) { 135200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String display = entry.getDisplayName(); 135300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String address = entry.getDestination(); 135400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 135500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira display = null; 135600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 135700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (address != null) { 135800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Tokenize out the address in case the address already 135900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // contained the username as well. 136000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 136100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (tokenized != null && tokenized.length > 0) { 136200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira address = tokenized[0].getAddress(); 136300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 136400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 136500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (!TextUtils.isEmpty(display)) { 136600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return display; 136700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else if (!TextUtils.isEmpty(address)){ 136800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return address; 136900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else { 137000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return new Rfc822Token(display, address, null).toString(); 137100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 13728659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 13738659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1374fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String displayText = createAddressText(entry); 1376a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (TextUtils.isEmpty(displayText)) { 1377a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira return null; 1378a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1379c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1380b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = displayText.length()-1; 1381c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1382c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1384c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 13856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 13866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1387c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 13886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1389c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1390c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1391c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1392c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1393c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1394c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1395c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 13998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 14008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1401c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1402c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1403c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1404c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1405c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1406c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 14070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 14080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 14091e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 14101e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 14111e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1415c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1416c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 14194221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 14204221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 14214221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 14224221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1423f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 14270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 14280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 14290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 14310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 14320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 14330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 14340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1435c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1436c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 14370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 14390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 14400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 14410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 14430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 144800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 14497a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14507a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14517a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 14527a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1453c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1454c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 145700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 145800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 145900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */ Collection<Long> getDataIds() { 146000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira final Set<Long> result = new HashSet<Long>(); 146100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip [] chips = getSortedRecipients(); 146200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (chips != null) { 146300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira for (RecipientChip chip : chips) { 146400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira result.add(chip.getDataId()); 146500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 146600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 146700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return result; 146883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 146983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 14704031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 147100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */RecipientChip[] getSortedRecipients() { 147200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] recips = getSpannable() 147300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .getSpans(0, getText().length(), RecipientChip.class); 14746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 147500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .asList(recips)); 14766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 14776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 14786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 14796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 14806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 14816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 14826f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 14836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 14846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 14856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 14866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 14876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 14886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 14896f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14906f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14916f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 14926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 14936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 14946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 14954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 14964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 14974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 14984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 15024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 15064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 15108684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 15118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 15124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 15144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1517a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1518a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ImageSpan getMoreChip() { 151922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 152022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 152122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 152222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 152322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 15248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1525045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1526045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1527045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 15288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1529a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1530a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void createMoreChip() { 1531bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1532bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1533bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1534bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 15356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 15366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 15376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 15386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 154083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 15410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 154583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 15464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1547c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1548c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1549c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1550c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1551c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1552c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 15534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 15544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 15554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 155622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira int adjustedHeight = height; 155722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira Layout layout = getLayout(); 155822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (layout != null) { 155922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira adjustedHeight -= layout.getLineDescent(0); 156022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 156122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 15624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 15644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 15656f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 15664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1567368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 15684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 15690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 15700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 15710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 15724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1573368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 15744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 15754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 15766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1577368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1578368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 15799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1580368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 15819024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1582368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1583368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 15849024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 15856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 15866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 15876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 15886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 158977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1590368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 15914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 159277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 159377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 159477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 15954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 159677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 15970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 15984e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15994e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 16008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 16018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 16028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 16038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1604a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1605a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /*package*/ void removeMoreChip() { 16064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 16074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 16084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 16094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 16104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 16114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 16124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 1613c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 161464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 161564077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 1616c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (recipients == null || recipients.length == 0) { 1617c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira return; 1618c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 161964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 16204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 16214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 16226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 16230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 16240fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 16256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 16266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 162764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 162864077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 162964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 163064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 163164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 163264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1633bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1634bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1635bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1636bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1637bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 16384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 16404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1644c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1645b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1646b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1647b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1648b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1649b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1653b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1655c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 165600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip selectChip(RecipientChip currentChip) { 16571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 16581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 16591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 16601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 16611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 16621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 16631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 16642f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 16651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1666b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1667b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1668b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1669b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1670b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1671b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1672b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1673b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1675b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1676fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 167883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 168083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 16818b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 168283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 16841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 16851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1686c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 16871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 16886ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1689b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1690b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 16911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 16921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 16931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 16941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 16951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 16961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 16971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 16981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 16991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 17001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1701b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 17021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 17041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 17051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 17061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 17091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 17101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 17111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 17136ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 17141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1715fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1716b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1717fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1718c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 17191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 17201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 17211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 17221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 17231174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 17241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1725e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 17261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 17271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 17281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 17291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 17301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 17311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 17351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 17381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 17391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 17401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 17411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1743b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1744b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1745b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1746b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 174800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private void unselectChip(RecipientChip chip) { 1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1751b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1752c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1753b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 17545753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 17555753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 17565753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 17575753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1759c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1760b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17618b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 17628b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 17638b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 17648b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17658b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 17668b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 17678b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1768c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1769b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1770b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1771b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1772b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1773c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1774b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1775c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1777b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1778b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1779b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1780b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1781b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1782b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1783b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1784b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1785b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1786b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1787b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1788b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1789b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 17903656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1792b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1793b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1794ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 1795ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /*pacakge*/ void removeChip(RecipientChip chip) { 1796b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1797b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1798b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1799b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1800b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int toDelete = spanEnd; 1801b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1802b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1803b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1804b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1805c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1806b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira // Always remove trailing spaces when removing a chip. 1807b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1808b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira toDelete++; 1809b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1810b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1811b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira text.delete(spanStart, toDelete); 1812b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1813b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1815b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1816c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1817b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1819b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1820b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 182100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 182200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) { 1823b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1824b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1826c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1827b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1829b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1830b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1831b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1832a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1833a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (start == -1 || end == -1) { 1834a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1835a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.insert(0, chipText); 1836a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } else { 1837a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (!TextUtils.isEmpty(chipText)) { 1838a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // There may be a space to replace with this chip's new 1839a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // associated 1840a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // space. Check for it 1841a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira int toReplace = end; 1842a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1843a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira && editable.charAt(toReplace) == ' ') { 1844a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira toReplace++; 1845a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1846a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, toReplace, chipText); 1847b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1848b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1850b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1851b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1852b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1853c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1854b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1855c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1857b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1858b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1860b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1863b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1865b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1866b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1867c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1868c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1870c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1871368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1872368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1873368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1874368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1875311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1876311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1877311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1878311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1879311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1880311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1881e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1882e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1883e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 18841e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 18851e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 18861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 18871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 18881e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 18891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 18901e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 18911e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 18921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 18931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 18951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 18961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 18981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 18991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 19001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1901368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 19045753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 19055753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 19065753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1909e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1911e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1913054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1915054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 191676ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1917054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1918054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1919054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1920054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1921054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1922054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1923e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1924e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1925e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1926e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1927e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1928e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1929e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1930e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1931e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 19323b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1933e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1934e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1935e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1936e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1937e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1938e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1939e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1940e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 19411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1942e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1943e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1944e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1945e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 194622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 1947e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1948e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 194977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 195049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira @Override 195149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira public boolean onTextContextMenuItem(int id) { 195249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (id == android.R.id.paste) { 195349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira removeTextChangedListener(mTextWatcher); 195449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 195549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Context.CLIPBOARD_SERVICE); 195649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ClipData clip = clipboard.getPrimaryClip(); 195749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (clip != null 195849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { 195949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira for (int i = 0; i < clip.getItemCount(); i++) { 196049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira CharSequence paste = clip.getItemAt(i).getText(); 196149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (paste != null) { 196249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int start = getSelectionStart(); 196349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int end = getSelectionEnd(); 196449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Editable editable = getText(); 196549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (start >= 0 && end >= 0 && start != end) { 196649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira editable.append(paste, start, end); 196749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } else { 196849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira editable.insert(end, paste); 196949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 197049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira handlePaste(); 197149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 197249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 197349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 197449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira mHandler.post(mAddTextWatcher); 197549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return true; 197649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 197749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return super.onTextContextMenuItem(id); 197849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 197949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 198049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 198149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /* package */void handlePaste() { 198249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String text = getText().toString(); 198349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 198449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String lastAddress = text.substring(originalTokenStart); 198549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenStart = originalTokenStart; 198649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int prevTokenStart = tokenStart; 198749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip findChip = null; 198849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != 0) { 198949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // There are things before this! 199049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart != 0 && findChip == null) { 199149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira prevTokenStart = tokenStart; 199249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = mTokenizer.findTokenStart(text, tokenStart); 199349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira findChip = findChip(tokenStart); 199449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 199549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != originalTokenStart) { 199649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (findChip != null) { 199749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = prevTokenStart; 199849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 199949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenEnd; 200049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip createdChip; 200149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart < originalTokenStart) { 200249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart)); 200349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira commitChip(tokenStart, tokenEnd, getText()); 200449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira createdChip = findChip(tokenStart); 200549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // +1 for the space at the end. 200649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = getSpannable().getSpanEnd(createdChip) + 1; 200749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 200849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 200949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 201049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Take a look at the last token. If the token has been completed with a 201149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // commit character, create a chip. 201249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (isCompletedToken(lastAddress)) { 201349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Editable editable = getText(); 201449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira commitChip(editable.toString().indexOf(lastAddress, originalTokenStart), editable 201549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira .length(), editable); 201649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 201749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 201849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 201949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 202049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /* package */int movePastTerminators(int tokenEnd) { 202149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd >= length()) { 202249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 202349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 202449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = getText().toString().charAt(tokenEnd); 202549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) { 202649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 202749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 202849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // This token had not only an end token character, but also a space 202949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // separating it from the next token. 203049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') { 203149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 203249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 203449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 203549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 203677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 203777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 203877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 203977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 204077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 204177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 204277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 204377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 204477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 204577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 204677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 204777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 204877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 204977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 205077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 205177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 205277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 205377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 205477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 20556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 205677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 205777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 205877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 205977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 206077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 206177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 206277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 206377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 206400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 206577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 206677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 206777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 206877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 206977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 207077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 20711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 207277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 207377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 20741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 20751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 207677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 207777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 207877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 207977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 208077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 208177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 208277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 208377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 208477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 208577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 208677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 208777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 208877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 208977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 209077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 209177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 209277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 209377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 209477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 2095b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 20962e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira oldText.removeSpan(chip); 2097b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 20986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 20996f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 2100b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 21016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 210277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 210477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 21062e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira setText(text); 210777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 210877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 210977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 211077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 211177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 211277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 211377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 211477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 211577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 211677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 211777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 211877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 211977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 212077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 212177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 212277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 212377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 212477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 212500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 212677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 212777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 212877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 212977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 21301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 213177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 21331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 21341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 213577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 213677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 213777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 213877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 213977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 214077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 2148b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 21496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 21506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 21516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 21526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 21536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 21546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 21556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 21566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 21576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 21596f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 2160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 2162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2166b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 2167b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2168b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2169b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2170b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2171b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2172b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 2173b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 2174b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 2175b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2176b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 2177b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 2178b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 2179b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 2180b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 2181b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 2182b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 2183b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2184b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2185b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2186b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 2187b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 2188b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 2189b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 2190b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 2191b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 2192b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 2193b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 2194b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 2195b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2196b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2197b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2198b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 2199b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2200b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2201b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2202b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2203b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2204b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 2205b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2206b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2207b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2208b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2209b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 2210b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2211b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2212b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2213b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2214b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2215b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 2216b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 2217b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2218b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2219b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2220b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 2221b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 2222b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 2223b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2224b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2225b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2226b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 222777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2228