RecipientEditTextView.java revision ec35d9975b61c84cda7081ebfce9e017229fb3d5
12d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/* 22d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Copyright (C) 2011 The Android Open Source Project 32d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 42d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Licensed under the Apache License, Version 2.0 (the "License"); 52d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * you may not use this file except in compliance with the License. 62d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * You may obtain a copy of the License at 72d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 82d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * http://www.apache.org/licenses/LICENSE-2.0 92d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * 102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * Unless required by applicable law or agreed to in writing, software 112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * distributed under the License is distributed on an "AS IS" BASIS, 122d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * See the License for the specific language governing permissions and 142d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * limitations under the License. 152d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 162d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 172d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereirapackage com.android.ex.chips; 182d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 19b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.app.Dialog; 20b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipData; 2149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereiraimport android.content.ClipDescription; 22b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager; 232d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context; 24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface; 25b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener; 26c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap; 271e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory; 28c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas; 291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix; 303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.graphics.Point; 31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 33c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 3577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask; 36156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler; 37156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message; 3822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable; 392d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 40572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 48c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 49c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 50c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 532d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 54c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 56a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Phamimport android.view.DragEvent; 58b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 60c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 614fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 624fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 65572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 66416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 69b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 70c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 71156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 722d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 73e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow; 74416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 75c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 76b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 77a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 79b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 806f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 816f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 8277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 84c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 85c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 90b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 91b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 92e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener, 93e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira PopupWindow.OnDismissListener { 94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 9600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 9700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 9800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 9900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 10000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 10400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 10500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static final long DISMISS_DELAY = 300; 10600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 1074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 1088faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // Visible for testing. 1098faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira /*package*/ static final int CHIP_LIMIT = 2; 1108faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 1118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira private static final int MAX_CHIPS_PARSED = 50; 1124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 11300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private static int sSelectedTextColor = -1; 11400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 11500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Resources for displaying chips. 116c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 117c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Drawable mInvalidChipBackground; 12100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 12200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Drawable mChipBackgroundPressed; 12300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 12400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private float mChipHeight; 12500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 12600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private float mChipFontSize; 12700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private Validator mValidator; 133c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 134c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 136c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1391e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 142c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 144054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 145a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 146156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 147156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 148c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 149c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 1508faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira private boolean mNoChips = false; 1518faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 152b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 153b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 15677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 15777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 160bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 161bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 163b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 164b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 165b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 166b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 167b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 168b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 169b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 170b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 171b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 172a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 173a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 174b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 175b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 176b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 177416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 17800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 18100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Obtain the enclosing scroll view, if it exists, so that the view can be 18200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // scrolled to show the last line of chips content. 183416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 184416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 18500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private boolean mTriedGettingScrollView; 186416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1873e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private boolean mDragEnabled = false; 1883e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 189e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 190e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 191e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 192e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 193e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 194e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 195e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 196e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 197e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 198e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 19977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 20077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 201a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 202a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 203a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 204a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 205a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 206a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 207a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 208a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 209a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 210a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira private Runnable mDelayedShrink = new Runnable() { 211a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 212a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira @Override 213a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira public void run() { 214a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira shrink(); 215a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 216a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 217a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira }; 218a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 2192d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 2202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 22177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 22277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 22377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 225e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAlternatesPopup.setOnDismissListener(this); 2261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 227e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAddressPopup.setOnDismissListener(this); 2280436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira mCopyDialog = new Dialog(context); 229b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 231b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 232b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 233368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 234e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 235b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 236b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 237b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 238368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 239b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 240b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 241b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 242b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 244c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 246156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 247156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 248156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 249156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 251156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 252156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 253156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 254156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 255156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2566ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 258b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 259ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 260cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 26100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ RecipientChip getLastChip() { 26200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip last = null; 26300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 26400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (chips != null && chips.length > 0) { 26500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira last = chips[chips.length - 1]; 266ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 26700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return last; 2684fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2694fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2704fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2714fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2724fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2734fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 27400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip last = getLastChip(); 2758faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (last != null && start < getSpannable().getSpanEnd(last)) { 27600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Grab the last chip and set the cursor to after it. 27700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length())); 278d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 279d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 280d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 281d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 28222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira @Override 28322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira public void onRestoreInstanceState(Parcelable state) { 28422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (!TextUtils.isEmpty(getText())) { 28522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(null); 28622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } else { 28722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(state); 28822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 28922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 29022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 29100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira @Override 2927c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira public Parcelable onSaveInstanceState() { 2937c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira // If the user changes orientation while they are editing, just roll back the selection. 2947c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira clearSelectedChip(); 2957c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira return super.onSaveInstanceState(); 2967c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira } 2977c61ef07ed148922a0b71c4e8ebb741cff2c5ae7Mindy Pereira 298c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 300c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 301c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 302c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 303c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 304c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 305c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 308001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 310c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 311c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 3124f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 31305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 314c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 315c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 316c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 317a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 318c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 319c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 320a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 321a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 322a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 323a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 3246ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 325c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 326c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 327d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 328d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 329b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 330d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 3314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 3324fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 334416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 3354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 338b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 339b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 340b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 341b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 342b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3445a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3455a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 348a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira if (getWidth() <= 0) { 349a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // We don't have the width yet which means the view hasn't been drawn yet 350a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // and there is no reason to attempt to commit chips yet. 351a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This focus lost must be the result of an orientation change 352a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // or an initial rendering. 353a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Re-post the shrink for later. 354a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.removeCallbacks(mDelayedShrink); 355a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mHandler.post(mDelayedShrink); 356a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira return; 357a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 358001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 359001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 360001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 361a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 362001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 363001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 364001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 365001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 366001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 367001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 368a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira Editable text = getText(); 369a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(text, start); 370a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // This token was already tokenized, so skip past the ending token. 3718a518e93aef58d5ced2d0223a5fd89b996930e26Mindy Pereira if (whatEnd < text.length() && text.charAt(whatEnd) == ',') { 372a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira whatEnd++; 373a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 374001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 375001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 376a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira int selEnd = getSelectionEnd(); 3774ef6086da2f4bcac5a6cba773f828734a399a879Mindy Pereira if (whatEnd != selEnd) { 378001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 379001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 380001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 381001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 382001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 383090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 384001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3874e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3904e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 39477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 39577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 39677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 39777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 39877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 39977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 4004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 4014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 4021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 403e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 404c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 405c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 406c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 407c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 408c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 411e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 4121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 4131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 4141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 415e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 417e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira float[] widths = new float[1]; 418e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira paint.getTextWidths(" ", widths); 41900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 420e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira calculateAvailableWidth(true) - deleteWidth - widths[0]); 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4221e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4231e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 4241e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4251e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 4291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 4301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 4321e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 4331e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 43477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 43597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 43697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 43797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 4381e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 439f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 440f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 441f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 442f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 443f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 444f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4461e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 452045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 453e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 454c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 457e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 459e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira float[] widths = new float[1]; 460e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira paint.getTextWidths(" ", widths); 46100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint, 462e73c4ce189c70e7f9f4d44b26fce458e3385988aMindy Pereira calculateAvailableWidth(false) - iconWidth - widths[0]); 4631a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4641a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4661e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 468c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 469c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 470c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 472045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 473045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 474045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 475045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4779024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4799024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 48090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 48190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 48290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 48390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 48490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 48590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 48690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 48790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 48890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4899024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4909024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4919024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4929024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4939024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4949024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4959024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4969024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 497ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira if (photo != null) { 498ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 499ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Rect backgroundPadding = new Rect(); 500ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground.getPadding(backgroundPadding); 501ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 502ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 0 + backgroundPadding.top, 503ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira width - backgroundPadding.right, 504ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira height - backgroundPadding.bottom); 505ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira Matrix matrix = new Matrix(); 506ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 507ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 508ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5109024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 5119024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 513e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 51497b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 515379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 51697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 5181e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 5191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 5201e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 5211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 522c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 52300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** 52400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira * Get the background drawable for a RecipientChip. 52500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira */ 52600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 52700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ Drawable getChipBackground(RecipientEntry contact) { 52800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 52900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mChipBackground : mInvalidChipBackground; 53000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 53100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 53297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 53397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 5343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham paint.getTextBounds(text, 0, text.length(), bounds); 53597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 53697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 53797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 53897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 53900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 5401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 5411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 5421e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 5431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 5451e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 546c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5471e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 54977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5521e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 553e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 554c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 556045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 559c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 560c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 56277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 563c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 564c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 56577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 566c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5672d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5682d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 570045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 571045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5725519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5735519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 575c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5765519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 577416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 57897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 57997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 580f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 581f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5892d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5902d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5924f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5934f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5944f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5964f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5971e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5991426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 6001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 6011426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 602c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 603b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 604b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 605c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 60643876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 607045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 608d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 609b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 610b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 611b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 612b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 613b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 614b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 615b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 616b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 617b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 618b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 619b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 620b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 621b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 622b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 623a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 624a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void setMoreItem(TextView moreItem) { 625a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira mMoreItem = moreItem; 626a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 627a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira 628ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 629ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 630ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipBackground(Drawable chipBackground) { 631ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipBackground = chipBackground; 632ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 633ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 634ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 635ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /* package */ void setChipHeight(int height) { 636ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira mChipHeight = height; 637ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira } 638ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira 639bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 640bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 641bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 642bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 643bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 644bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 645bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 646bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 647bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 648bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 649bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 650c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 651c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 652c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 65353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (width != 0 && height != 0) { 65453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount > 0) { 65553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira postHandlePendingChips(); 65653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } else { 65753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira checkChipWidths(); 65853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 6597bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 66077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 66100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (mScrollView == null && !mTriedGettingScrollView) { 662416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 663416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 664416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 665416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 666416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 667416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 668416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 66900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira mTriedGettingScrollView = true; 670416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 671c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 672c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 673a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 674a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 675a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 676a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 677a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 67853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira private void checkChipWidths() { 67953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Check the widths of the associated chips. 68053958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 68153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (chips != null) { 68253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira Rect bounds; 68353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira for (RecipientChip chip : chips) { 68453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira bounds = chip.getDrawable().getBounds(); 68553958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) { 68653958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira // Need to redraw that chip. 68753958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira replaceChip(chip, chip.getEntry()); 68853958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 68953958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 690a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 69153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 69253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 6938faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // Visible for testing. 6948faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira /*package*/ void handlePendingChips() { 6958faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (getViewWidth() <= 0) { 6967bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6977bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6987bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6997bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 7007bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 70153958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira if (mPendingChipsCount <= 0) { 70253958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira return; 70353958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira } 70453958d6d0ba2a0765fd3d272295b81c6d89a68d8Mindy Pereira 705a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 706a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 707a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 7088faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (mPendingChipsCount <= MAX_CHIPS_PARSED) { 7098faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 7108faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira String current = mPendingChips.get(i); 7118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int tokenStart = editable.toString().indexOf(current); 7128faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int tokenEnd = tokenStart + current.length(); 7138faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (tokenStart >= 0) { 7148faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // When we have a valid token, include it with the token 7158faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // to the left. 7168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (tokenEnd < editable.length() - 2 7178faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 7188faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira tokenEnd++; 7198faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 7208faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 721a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 7228faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira mPendingChipsCount--; 7230fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7248faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira sanitizeEnd(); 7258faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } else { 7268faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira mNoChips = true; 7270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7288faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 72922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 730a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 731a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 732a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 733a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 734a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 735a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 736a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 737a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 738a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 73977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 740a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 741a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 742a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 743a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 74422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 745a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 74677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 74777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 748a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 749a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 7500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7538faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // Visible for testing. 7548faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira /*package*/ int getViewWidth() { 7558faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return getWidth(); 7568faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 7578faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 7580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 7600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 76100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 76200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void sanitizeEnd() { 7630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 76400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 7650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 7660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 7670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 76822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 7690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 7700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 7710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 77200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira lastSpan = getLastChip(); 7730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 7750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 7760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 7770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 7780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 7790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 7800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 7810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 7820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 7840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 7890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 7900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 7910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 7920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 7931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 7941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 798b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 8000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 8010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 8020fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 804d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 80500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String destText = createAddressText(entry); 806d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 807b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int textLength = destText.length() - 1; 808d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 809d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 810d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 811d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 812d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 8138faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (!mNoChips) { 8148faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira chip = constructChipSpan(entry, start, false); 8158faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 8168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 817d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 818d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 819d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 820d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 821d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 822d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 82322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 82422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 82522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 8266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 827d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 828d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 82977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 8300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 8320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 833d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 834d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 835d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 8371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 8386ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 839454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 840454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 8411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 8421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 8431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 8446ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 8456ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 8466ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 8476ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 8486ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 8491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8516ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 8526ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 853a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 8546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 8556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 856a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 857a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 858a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 859a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 860a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 861a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 862a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 863a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 864a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 865a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 866a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 867a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 868a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 869a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 870490556a764a879cd0eaff358e90705cc1335c92eErik } 871d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 8726ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 873454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 874a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 875a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 8761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 8771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 8786ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 8796ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 8806ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 8816ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 8821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 8831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 8841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 8851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 8860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 8880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 8890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 890c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 891c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 892c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 893c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 894c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 895c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 9008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 9018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 9028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 9048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 9058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 9068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 907c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 908c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 909c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 910c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 911c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 9148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 915c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 91695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 91795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 91895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 91995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 92095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 92195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 92295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 9238684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 9258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 9268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 9278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 9288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 9298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 9308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 93195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 932c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 933c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 934c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 935c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 937d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 940e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 941e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 942e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 943e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 944e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 945e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 94795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 948e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 949e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 950e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 951e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 952e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 953e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 954e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 955e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 956e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 957e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 958e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 963e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 964e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 965e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 966e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 967e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 968e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 969e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 970e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 971e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 972045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 973045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 974045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 975045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 976045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 977045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 978045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 9798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 9804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 981045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 9824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 983dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 985e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 989e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 9914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 992e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 993e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 994e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 995e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 996e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 997e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 998e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 1000e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 1001e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 1002e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 10034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1004054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 1005e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 10064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1007e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 100849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ListAdapter adapter = getAdapter(); 100949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (adapter != null && adapter.getCount() > 0 && enoughToFilter() 101049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira && end == getSelectionEnd()) { 1011e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 1012e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 1013e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 1014e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 1015e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 1016e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 101749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (editable.length() > tokenEnd + 1) { 101849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char charAt = editable.charAt(tokenEnd + 1); 101949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) { 102049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 102149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1022a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira } 1023b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 1024e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 1025e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 10261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 1027d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 1028d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 1029d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 103071fa24bfb96744cf28d368d58172c14638e14693Mindy Pereira if (chipText != null && start > -1 && end > -1) { 10315ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 10325ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 1033d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 103449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Only dismiss the dropdown if it is related to the text we 103549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // just committed. 103649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // For paste, it may not be as there are possibly multiple 103749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // tokens being added. 103849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (end == getSelectionEnd()) { 103949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira dismissDropDown(); 104049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 1041f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 10424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 1043d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1044d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 10454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 1046d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1047d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 10484031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 10494031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ void sanitizeBetween() { 1050f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 10514031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira RecipientChip[] recips = getSortedRecipients(); 1052f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 1053f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 1054f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 1055f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 1056f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 1057f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1058f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 1059f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 1060f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 1061f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 1062c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira Editable text = getText(); 10634031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira if (startLooking == -1 || startLooking > text.length() - 1) { 1064c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira // There is nothing after this chip. 1065c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira return; 1066c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira } 1067c7afc9841ee6dd3bc5e19ee7269fecd466a8c6e9Mindy Pereira if (text.charAt(startLooking) == ' ') { 1068f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 1069f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1070f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1071ec35d9975b61c84cda7081ebfce9e017229fb3d5Mindy Pereira if (startLooking >= 0 && end >= 0 && startLooking < end) { 1072f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 1073f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1074f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1075f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 1076f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 1077e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 10788faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 10791e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 10801e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 10811e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 10828faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (mNoChips) { 10838faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return true; 10848faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 10851e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 10861e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 10871e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 108805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 10891e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 1090e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1091e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1092e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 109339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 109439f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 109539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 109639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 109739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 1098e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 1099e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 1100e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 1101e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 1102e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 11033b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 11043b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 11053b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 11063b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 11076fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira int selEnd = getSelectionEnd(); 11086fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira if (chipText != null && start > -1 && selEnd > -1) { 11096fe9af5b1cc380243f0fad9354ab7bd41ac2983bMindy Pereira editable.replace(start, selEnd, chipText); 1110a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 11113b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 1112054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 111305522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 111405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 11158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 11178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 11188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1119c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 1121b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 1122b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1132c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 11332d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 11342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 11354031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 11364031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira /* package */ Spannable getSpannable() { 1137ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1140b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1141b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1142b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1143b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1147b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1153c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1154c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1155c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1156c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 115749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (enoughToFilter() && !isCompletedToken(text)) { 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1163c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 117149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 117249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /*package*/ boolean isCompletedToken(CharSequence text) { 117349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (TextUtils.isEmpty(text)) { 117449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 117549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 117649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Check to see if this is a completed token before filtering. 117749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int end = text.length(); 117849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int start = mTokenizer.findTokenStart(text, end); 117949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String token = text.toString().substring(start, end).trim(); 118049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (!TextUtils.isEmpty(token)) { 118149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = token.charAt(token.length() - 1); 118249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON; 118349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 118449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return false; 118549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 118649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 1187c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1189b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 119236d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1194c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11968684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 11978684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 11988684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 11998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 12008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 12018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 12028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1205d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1206d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1207d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1208d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1210d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 12126caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1213b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1214b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 12150436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1216c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1217c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1218c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1219c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1220c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1221c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1222c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1223c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1224b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1225c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 12268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 12278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1229c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1230b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1231c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1232c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1233c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1234416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 12355753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 12365753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 12375753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1242c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1243c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1244c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1245c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1246416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1247416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1248c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1249416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1250416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1251416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1252b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1253b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1254b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1255c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1256b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1258b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1259e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 126077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1261416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1262b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1263368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1264e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1265e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1266b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1267b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1268b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1269b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1270b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1271b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1272b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1273b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1274b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1275b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1276b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1277b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1278b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1279e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1280e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1281e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1282e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1283e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1284e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1285b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1286b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1287b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1288b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1289b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 12901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 12911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 12921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 12931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 12941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1295a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1296b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1297b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1298b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1299b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1300b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1301e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1302b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1303b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1305c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1306c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1307c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1308c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1309c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1311c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1314c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1315c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1316c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1317c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1318c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1319c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1320c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13214fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 13224fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 13234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1324c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1325c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 13264fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1327c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1328c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1329c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1330c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1331c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1332c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1333c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13344fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 13354fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 13364fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 13374fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 13394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 13404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1341c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1342c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1343c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1344c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1345c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1346b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1347b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1348b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1352c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1353c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1354c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13554031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 135600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to add to the list of addresses. 135700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createAddressText(RecipientEntry entry) { 13588659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 13598659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 13608659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 13618659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 13628659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1363a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1364a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1365a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1366c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 1367c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (tokenized != null && tokenized.length > 0) { 1368c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira address = tokenized[0].getAddress(); 1369c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 1370a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 13718659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 137200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String trimmedDisplayText = token.toString().trim(); 13738659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 13748659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 137500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .terminateToken(trimmedDisplayText) : trimmedDisplayText; 137600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 137700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 137800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 137900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Use this method to generate text to display in a chip. 138000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ String createChipDisplayText(RecipientEntry entry) { 138100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String display = entry.getDisplayName(); 138200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String address = entry.getDestination(); 138300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 138400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira display = null; 138500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 138600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (address != null) { 138700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Tokenize out the address in case the address already 138800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // contained the username as well. 138900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address); 139000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (tokenized != null && tokenized.length > 0) { 139100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira address = tokenized[0].getAddress(); 139200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 139300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 139400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (!TextUtils.isEmpty(display)) { 139500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return display; 139600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else if (!TextUtils.isEmpty(address)){ 139700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return address; 139800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } else { 139900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return new Rfc822Token(display, address, null).toString(); 140000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 14018659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 14028659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1403fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 140400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira String displayText = createAddressText(entry); 1405a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (TextUtils.isEmpty(displayText)) { 1406a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira return null; 1407a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 14088faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira SpannableString chipText = null; 1409c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1410c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 14128faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int textLength = displayText.length()-1; 14138faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira chipText = new SpannableString(displayText); 14148faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (!mNoChips) { 14158faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira try { 14168faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 14178faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira chipText.setSpan(chip, 0, textLength, 14188faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 14198faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira chip.setOriginalText(chipText.toString()); 14208faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } catch (NullPointerException e) { 14218faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Log.e(TAG, e.getMessage(), e); 14228faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return null; 14238faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 1424c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1425c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1426c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1427c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 14308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 14318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1435c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1436c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1437c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 14380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 14390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 14401e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 14411e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 14421e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1443c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1444c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1446c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1447c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1449c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 14504221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 1451920c95167574395b1ff9ecc16e41e13853269bbcMindy Pereira if (chip != null && start >= 0 && end >= 0) { 14524221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 14534221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1454f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1455c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1456c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 14580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 14590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 14600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 14620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 14630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 14640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 14650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 14661e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira if (RecipientEntry.isCreatedRecipient(item.getContactId()) 14671e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira && (TextUtils.isEmpty(item.getDisplayName()) 14681e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 14691e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira || (mValidator != null && !mValidator.isValid(destination)))) { 14700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 14710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 14720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 14730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 14750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 14760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1477c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 148000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] chips = getSortedRecipients(); 14817a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 14827a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 14837a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 14847a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1485c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1487c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1488c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 148900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira 149000be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 149100be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */ Collection<Long> getDataIds() { 149200be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira final Set<Long> result = new HashSet<Long>(); 149300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip [] chips = getSortedRecipients(); 149400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira if (chips != null) { 149500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira for (RecipientChip chip : chips) { 149600be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira result.add(chip.getDataId()); 149700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 149800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira } 149900be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira return result; 150083e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 150183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 15024031c89f46edea42c64e183e46d49484f1f203c0Mindy Pereira // Visible for testing. 150300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /* package */RecipientChip[] getSortedRecipients() { 150400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira RecipientChip[] recips = getSpannable() 150500be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .getSpans(0, getText().length(), RecipientChip.class); 15066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 150700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira .asList(recips)); 15086f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 15096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 15106f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 15116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 15126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 15136f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 15146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 15156f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 15166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 15176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 15186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 15196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 15206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 15216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 15246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 15256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 15266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 15274e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 15294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 15344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15364e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 15384e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 15418684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 15428684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 15438684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 15444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 15454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 15464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 15474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 15484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1549a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1550a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ImageSpan getMoreChip() { 155122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 155222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 155322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 155422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 155522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 15568faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira private MoreImageSpan createMoreSpan(int count) { 15578faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira String moreText = String.format(mMoreItem.getText().toString(), count); 15588faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 15598faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 15608faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 15618faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 15628faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira + mMoreItem.getPaddingRight(); 15638faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int height = getLineHeight(); 15648faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 15658faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Canvas canvas = new Canvas(drawable); 15668faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int adjustedHeight = height; 15678faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Layout layout = getLayout(); 15688faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (layout != null) { 15698faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira adjustedHeight -= layout.getLineDescent(0); 15708faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 15718faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 15728faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 15738faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 15748faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira result.setBounds(0, 0, width, height); 15758faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return new MoreImageSpan(result); 15768faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 15778faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 15788faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // Visible for testing. 15798faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira /*package*/ void createMoreChipPlainText() { 15808faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // Take the first <= CHIP_LIMIT addresses and get to the end of the second one. 15818faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Editable text = getText(); 15828faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int start = 0; 15838faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int end = start; 15848faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira for (int i = 0; i < CHIP_LIMIT; i++) { 15858faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira end = movePastTerminators(mTokenizer.findTokenEnd(text, start)); 15868faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira start = end; // move to the next token and get its end. 15878faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 15888faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // Now, count total addresses. 15898faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira start = 0; 15908faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int tokenCount = countTokens(text); 15918faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT); 15928faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(end, text.length())); 15938faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 15948faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira text.replace(end, text.length(), chipText); 15958faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira mMoreChip = moreSpan; 15968faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 15978faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 15988faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira // Visible for testing. 15998faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira /* package */int countTokens(Editable text) { 16008faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int tokenCount = 0; 16018faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira int start = 0; 16028faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira while (start < text.length()) { 16038faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira start = movePastTerminators(mTokenizer.findTokenEnd(text, start)); 16048faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira tokenCount++; 16058faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (start >= text.length()) { 16068faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira break; 16078faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 16088faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 16098faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return tokenCount; 16108faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 16118faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 16128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1613045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1614045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1615045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 16168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1617a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1618a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /* package */ void createMoreChip() { 16198faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (mNoChips) { 16208faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira createMoreChipPlainText(); 16218faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return; 16228faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 16238faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 1624bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1625bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1626bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1627bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 16286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 16296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 16306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 16316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 16326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 16338faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira 163483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 16350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 16360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 16374e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 163983e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 16404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 16418faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira MoreImageSpan moreSpan = createMoreSpan(overage); 1642368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 16434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 16444e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 16456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1646368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1647368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 16489024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1649368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 16509024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1651368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1652368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 16539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 16546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 16556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 16566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 16576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 165877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1659368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 16604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16618faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (totalReplaceEnd < text.length()) { 16628faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira totalReplaceEnd = text.length(); 16638faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 166477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 166577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 166677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 16674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 166877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 16690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 16704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 16714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 16728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 16738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 16748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 16758684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1676a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira // Visible for testing. 1677a3806ce9e97ef635dc6d4495f9664b690de74960Mindy Pereira /*package*/ void removeMoreChip() { 16784e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 16794e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 16804e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 16814e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 16824e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 16834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 16844e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 1685c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 168664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 168764077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 1688c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira if (recipients == null || recipients.length == 0) { 1689c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira return; 1690c10739cc694a0248b6187de2d7b9e8d2298bf0d3Mindy Pereira } 169164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 16924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 16934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 16946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 16950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 16960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 16976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 16986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 169964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 170064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 170164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 170264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 170364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 170464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1705bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1706bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1707bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1708bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1709bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 17104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 17114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 17124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 17134e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 17144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 17154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1716c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1717b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1718b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1719b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1722b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1724b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1727c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 172800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private RecipientChip selectChip(RecipientChip currentChip) { 17291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 17301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 17311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 17321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 17331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 17341174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 17351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 17362f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 17371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1738b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1739b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1740b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1741b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1742b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 17438faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (mNoChips) { 17448faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return null; 17458faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 1746b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1747b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1748b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1749b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1750b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1751fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1752b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 175383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1754b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 175583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 17568b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 175783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1758b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 17591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 17601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1761c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 17621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 17636ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1764b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1765b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 17661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 17671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 17681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 17691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 17701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 17711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 17721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 17731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 17741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 17751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1776b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 17771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 17781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 17791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 17801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 17811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 17841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 17851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 17861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 17871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 17886ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 17891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1790fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1791b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1792fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1793c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 17941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 17951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 17961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 17971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 17981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 17991174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1800e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 18011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 18021174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 18031174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 18041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 18051174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 18061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 18071174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 18081174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 18091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 18101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 18111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 18121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 18131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 18141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 18151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 18161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 18171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1818b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1819b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 18208faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira * the chip without a delete icon and with an unfocused background. This is 18218faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira * called when the RecipientChip no longer has focus. 1822b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 182300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira private void unselectChip(RecipientChip chip) { 1824b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1825b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1826b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1827c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1828b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 18298faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing"); 18305753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 18315753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1832b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1833c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1834b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 18358b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 18368b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 18378faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (!mNoChips) { 18388faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 18398faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 18408faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 18418b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 18428b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 18438b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1844c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1845b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1846b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1847b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1848b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1850b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1851c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1852b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1853b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1854b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1855b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1856b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1857b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1858b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1859b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1860b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1861b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1862b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1863b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1864b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1865b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 18663656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1867b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1868b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1869b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1870ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira // Visible for testing. 1871ae7e41106f289f2668c54fff6c457c6fc91ab4a9Mindy Pereira /*pacakge*/ void removeChip(RecipientChip chip) { 1872b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1873b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1874b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1875b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1876b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira int toDelete = spanEnd; 1877b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1878b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1879b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1880b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1881c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1882b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira // Always remove trailing spaces when removing a chip. 1883b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1884b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira toDelete++; 1885b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1886b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1887b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira text.delete(spanStart, toDelete); 1888b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1889b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1890c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1891b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1892c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1893b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1894b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1895b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1896b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 189700be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira // Visible for testing. 189800be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) { 1899b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1900b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1901b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1902c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1903b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1904b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1905b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1906b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1907b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1908a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (chipText != null) { 1909a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (start == -1 || end == -1) { 1910a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1911a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.insert(0, chipText); 1912a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } else { 1913a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira if (!TextUtils.isEmpty(chipText)) { 1914a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // There may be a space to replace with this chip's new 1915a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // associated 1916a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira // space. Check for it 1917a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira int toReplace = end; 1918a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1919a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira && editable.charAt(toReplace) == ' ') { 1920a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira toReplace++; 1921a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira } 1922a1ecf3cc31e72b56d51690ae6524e8a26e9dd358Mindy Pereira editable.replace(start, toReplace, chipText); 1923b3e42aaf97e476719cede047394718fd001fd79bMindy Pereira } 1924b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1925c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1926b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1927b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1928b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1930b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1931c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1932b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1933b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1934b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1935b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1936b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1937b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1938b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1939b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1940b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1941b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1942b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1943c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1944c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1945b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1946c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1947368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1948368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1949368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1950368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1951311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1952311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1953311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1954311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1955311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1956311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1957e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1958e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1959e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 19601e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 19611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 19621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 19631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 19641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 19651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 19661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 19671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 19681e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 19691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19701e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 19711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 19721e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19731e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 19741e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 19751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 19761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1977368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 19805753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 19815753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 19825753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1983e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1984e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1985e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1986e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1987e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1988e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1989054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1990e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1991054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 199276ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1993054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1994054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1995054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1996054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1997054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1998054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1999e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 2000e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 2001e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 2002e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 2003e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 2004e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 2005e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 2006e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 2007e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 20083b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 2009e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 2010e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 2011e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 2012e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 2013e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 2014e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 2015e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 2016e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 20171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 2018e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 2019e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 2020e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 2021e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 202222b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 2023e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 2024e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 202577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 20263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 20273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}. 20283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 20293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private void handlePasteClip(ClipData clip) { 20303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham removeTextChangedListener(mTextWatcher); 20313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 20323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){ 20333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham for (int i = 0; i < clip.getItemCount(); i++) { 20343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham CharSequence paste = clip.getItemAt(i).getText(); 20353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (paste != null) { 20363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham int start = getSelectionStart(); 20373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham int end = getSelectionEnd(); 20383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham Editable editable = getText(); 20393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (start >= 0 && end >= 0 && start != end) { 20403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham editable.append(paste, start, end); 20413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } else { 20423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham editable.insert(end, paste); 20433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 20443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteAndReplace(); 20453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 20463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 20473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 20483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 20493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mHandler.post(mAddTextWatcher); 20503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 20513e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 205249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira @Override 205349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira public boolean onTextContextMenuItem(int id) { 205449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (id == android.R.id.paste) { 205549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 205649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Context.CLIPBOARD_SERVICE); 20573e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteClip(clipboard.getPrimaryClip()); 205849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return true; 205949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 206049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return super.onTextContextMenuItem(id); 206149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 206249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 20631e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira private void handlePasteAndReplace() { 20641e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira ArrayList<RecipientChip> created = handlePaste(); 20651e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira if (created != null && created.size() > 0) { 20661e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira // Perform reverse lookups on the pasted contacts. 20671e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira IndividualReplacementTask replace = new IndividualReplacementTask(); 20681e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira replace.execute(created); 20691e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira } 20701e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira } 20711e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira 207249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 20731e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira /* package */ArrayList<RecipientChip> handlePaste() { 207449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String text = getText().toString(); 207549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 207649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira String lastAddress = text.substring(originalTokenStart); 207749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenStart = originalTokenStart; 207849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int prevTokenStart = tokenStart; 207949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip findChip = null; 20801e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira ArrayList<RecipientChip> created = new ArrayList<RecipientChip>(); 208149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != 0) { 208249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // There are things before this! 208349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart != 0 && findChip == null) { 208449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira prevTokenStart = tokenStart; 208549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = mTokenizer.findTokenStart(text, tokenStart); 208649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira findChip = findChip(tokenStart); 208749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 208849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenStart != originalTokenStart) { 208949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (findChip != null) { 209049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = prevTokenStart; 209149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 209249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira int tokenEnd; 209349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira RecipientChip createdChip; 209449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira while (tokenStart < originalTokenStart) { 209549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart)); 209649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira commitChip(tokenStart, tokenEnd, getText()); 209749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira createdChip = findChip(tokenStart); 209849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // +1 for the space at the end. 209949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenStart = getSpannable().getSpanEnd(createdChip) + 1; 21001e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira created.add(createdChip); 210149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 210249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 210349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 210449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Take a look at the last token. If the token has been completed with a 210549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // commit character, create a chip. 210649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (isCompletedToken(lastAddress)) { 210749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira Editable editable = getText(); 21081e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart); 21091e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira commitChip(tokenStart, editable.length(), editable); 21101e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira created.add(findChip(tokenStart)); 211149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 21121e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira return created; 211349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 211449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 211549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // Visible for testing. 211649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira /* package */int movePastTerminators(int tokenEnd) { 211749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd >= length()) { 211849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 211949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 212049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira char atEnd = getText().toString().charAt(tokenEnd); 212149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) { 212249ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 212349ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 212449ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // This token had not only an end token character, but also a space 212549ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira // separating it from the next token. 212649ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') { 212749ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira tokenEnd++; 212849ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 212949ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira return tokenEnd; 213049ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira } 213149ce9866ae314b3513e17008f107ceded23f9cf0Mindy Pereira 213277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 213377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 213477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 21358faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira if (mNoChips) { 21368faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira return null; 21378faa56ff909edb20154219f8d2ba6feecf1b5238Mindy Pereira } 213877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 213977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 214077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 214177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 214277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 214477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 214577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 214677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 214777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 214877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 214977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 215077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 215177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 215277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 215377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 21546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 215577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 215677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 215777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 215877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 215977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 216077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 216177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 216277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 216300be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 216477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 216577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 216677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 216777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 216877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 216977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 21701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 217177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 217277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 21731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 21741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 217577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 217677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 217777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 217877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 217977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 218077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 218177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 218277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 218377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 218477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 218577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 218677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 218777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 218877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 218977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 219077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 219177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 219277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 219377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 2194b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 21952e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira oldText.removeSpan(chip); 2196b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 21976f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 21986f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 2199b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 22006f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 220177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 220277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 220377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 220477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 22052e157c2b6e02a8648a87aa33163ecb89a641c2b0Mindy Pereira setText(text); 220677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 220777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 220877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 220977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 221077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 221177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 221277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 221377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 221477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 221577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 221677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 221777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 221877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 221977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 222077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 222177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 222277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 222377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 222400be4ce28569ae7d1871a2f95453b0e6a6a47852Mindy Pereira addresses[i] = createAddressText(originalRecipients.get(i).getEntry()); 222577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 222677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 222777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 222877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 22291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 223077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 223177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 22321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 22331e12ddb8232423b09b8df6c59f2e65189a0eea0aMindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase())); 223477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 223577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 223677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 223777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 223877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 223977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 224077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 224177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 224277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 224377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 224477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 224577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 224677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 2247b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 22486f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 22496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 22506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 22516f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 22526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 22536f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 22546f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 22556f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 22566f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 22576f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 22586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 2259b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2260b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 2261b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2262b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2263b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2264b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2265b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 2266b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2267b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2268b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2269b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2270b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2271b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 2272b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 2273b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 2274b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2275b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 2276b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 2277b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 2278b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 2279b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 22803e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham if (mDragEnabled) { 22813e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Start drag-and-drop for the selected chip. 22823e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham startDrag(currentChip); 22833e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } else { 22843e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Copy the selected chip email address. 22853e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham showCopyDialog(currentChip.getEntry().getDestination()); 22863e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22873e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22883e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22893e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22903e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22913e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Enables drag-and-drop for chips. 22923e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 22933e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void enableDrag() { 22943e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mDragEnabled = true; 22953e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 22963e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 22973e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 22983e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Starts drag-and-drop for the selected chip. 22993e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 23003e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private void startDrag(RecipientChip currentChip) { 23013e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham String address = currentChip.getEntry().getDestination(); 23023e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA); 23033e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 23043e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Start drag mode. 23053e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham startDrag(data, new RecipientChipShadow(currentChip), null, 0); 23063e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 23073e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Remove the current chip, so drag-and-drop will result in a move. 23083e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // TODO (phamm): consider readd this chip if it's dropped outside a target. 23093e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham removeChip(currentChip); 23103e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 23113e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 23123e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 23133e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Handles drag event. 23143e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 23153e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 23163e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public boolean onDragEvent(DragEvent event) { 23173e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham switch (event.getAction()) { 23183e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DRAG_STARTED: 23193e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham // Only handle plain text drag and drop. 23203e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); 23213e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DRAG_ENTERED: 23223e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham requestFocus(); 23233e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return true; 23243e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham case DragEvent.ACTION_DROP: 23253e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham handlePasteClip(event.getClipData()); 23263e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return true; 23273e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 23283e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham return false; 23293e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 23303e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 23313e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham /** 23323e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham * Drag shadow for a {@link RecipientChip}. 23333e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham */ 23343e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private final class RecipientChipShadow extends DragShadowBuilder { 23353e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham private final RecipientChip mChip; 23363e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 23373e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public RecipientChipShadow(RecipientChip chip) { 23383e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mChip = chip; 23393e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 23403e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 23413e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 23423e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { 23433e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham Rect rect = mChip.getDrawable().getBounds(); 23443e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham shadowSize.set(rect.width(), rect.height()); 23453e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham shadowTouchPoint.set(rect.centerX(), rect.centerY()); 23463e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham } 23473e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham 23483e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham @Override 23493e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham public void onDrawShadow(Canvas canvas) { 23503e9631982511d9692e4bc0dbd724240fae91cf1fMinh Pham mChip.getDrawable().draw(canvas); 2351b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2352b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2353b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2354b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 2355b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 2356b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 2357b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 2358b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 2359b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 2360b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 2361b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 2362b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 2363b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2364b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2365b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2366b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 2367b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2368b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2369b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2370b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2371b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2372b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 2373b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2374b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2375b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2376b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2377b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 2378b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 2379b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 2380b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2381b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2382b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2383b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 2384b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 2385b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 2386b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 2387b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 2388b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 2389b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 2390b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 2391b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2392b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2393b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2394b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 239577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2396