RecipientEditTextView.java revision f6b6b0a26cdebf436dbee3eb4a7118081db7b4cc
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; 21b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.ClipboardManager; 222d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.content.Context; 23b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface; 24b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.content.DialogInterface.OnDismissListener; 25c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Bitmap; 261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.BitmapFactory; 27c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Canvas; 281e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.Matrix; 29c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.Rect; 301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereiraimport android.graphics.RectF; 31c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.graphics.drawable.BitmapDrawable; 322d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.graphics.drawable.Drawable; 3377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.os.AsyncTask; 34156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Handler; 35156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.os.Message; 3622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereiraimport android.os.Parcelable; 372d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 38572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 41c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 45c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 46c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 48c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 512d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 52c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 534fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 54a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 55b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 56c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 57c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 594fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 61c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 62572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 63416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 65c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 66cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable; 67b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 68c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 69156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 702d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 71e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereiraimport android.widget.PopupWindow; 72416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 73c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 74b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 75a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 77b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 8077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 82c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 83c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 862d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 88b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 89b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 90e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener, 91e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira PopupWindow.OnDismissListener { 92c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 93c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 94c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackgroundPressed; 107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 111c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1121e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1131e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1154e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 116c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 118054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 119a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipHeight; 121e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 122e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipFontSize; 123e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 1248684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private Validator mValidator; 1258684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 126045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira private Drawable mInvalidChipBackground; 127045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 131156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 132156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static final long DISMISS_DELAY = 300; 133156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 134c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 135c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 13677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira private static int sSelectedTextColor = -1; 13777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira 13805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 13905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 14005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 141c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 142e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 143dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 144b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 14877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 14977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 152bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 153bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 160b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 161b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 162b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 164a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 165a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 166b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 167b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 168b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 170e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 171e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 173416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 174416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private boolean mTried; 175416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 183e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 184e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 185e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 18677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 18777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 194a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 195a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 196a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 1972d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 1982d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 19977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 20077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 20177db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 202b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 203e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAlternatesPopup.setOnDismissListener(this); 2041174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 205e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira mAddressPopup.setOnDismissListener(this); 2060436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira mCopyDialog = new Dialog(context); 207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 209b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 211368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 212e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 214b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 215b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 216368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 217b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 218b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 219b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 220b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 222c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2234fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 227156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 228b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 229156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 230156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 231156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 232156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 233156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2346ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2356ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 236b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 237ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 238cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 239ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik @Override 240ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 241ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik super.setAdapter(adapter); 242ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik if (adapter == null) { 243ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return; 244ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 2454fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2464fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2474fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2504fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 25183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Spannable span = getSpannable(); 25283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = getText().length(); 25383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class); 25483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (chips != null && chips.length > 0) { 2554fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (chips != null && chips.length > 0) { 2564fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // Grab the last chip and set the cursor to after it. 257b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength)); 2584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 259d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 260d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 261d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 262d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 26322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira @Override 26422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira public void onRestoreInstanceState(Parcelable state) { 26522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (!TextUtils.isEmpty(getText())) { 26622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(null); 26722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } else { 26822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira super.onRestoreInstanceState(state); 26922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 27022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 27122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 272c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 273c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 275c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 276c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 277c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 278c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 279c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 280001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 281001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 282001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 283001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 284c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 285c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 2864f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 28705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 288c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 289c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 290c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 291a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 292c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 293c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 294a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 295a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 296a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 297a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 2986ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 299c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 300c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 301d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 302d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 303b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 304d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 3054e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 3064fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 3074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 308416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 3094fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 3102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 3112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 312b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 313b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 314b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 315b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 316b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3174e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3185a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira if (mSelectedChip != null 3195a197cb9af43e1db6b882d89c23949577ceba9a4Mindy Pereira && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) { 3204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 324001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 325a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 326001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 327001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 328001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 329001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 330001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 331001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 332001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 333001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 334001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 335001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (whatEnd != getSelectionEnd()) { 336001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 337001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 338001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 339001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 340001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 341090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 342001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3454e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3464e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3474e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 35277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 35377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 35477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 35577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 35677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 35777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 361e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 362c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 363c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 364c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 365c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 366c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 368c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 369e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 373e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 3761e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 3771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 3801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 3811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 3821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 383c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 3851e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 3861e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 3871e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 3881e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 3891e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 39077db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 39197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 39297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 39397b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String) ellipsizedText, paint, height), paint); 3941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 395f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 396f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackgroundPressed.getPadding(backgroundPadding); 397f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left, 398f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 399f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 400f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 4011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 4021e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 4031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4061e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 407c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 408045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 409045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 410045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 411045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 412a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 413045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackground : mInvalidChipBackground; 414045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 415045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 416e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 417c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 418c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 419c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 420e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4211e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 4224221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira String displayText = 4234221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() : 4244221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : ""; 4254221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence ellipsizedText = ellipsizeText(displayText, paint, 4261e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4271a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4281a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4291e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4301e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4311e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 432c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 433c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 434c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 435c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 436045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 437045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 438045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 439045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4401e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 44490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 44590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 44690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 44790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 44890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 44990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 45090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 45190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 45290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4539024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4549024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4559024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4569024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4579024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4589024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4599024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4609024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 4619024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Matrix matrix = new Matrix(); 4629024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 463f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira Rect backgroundPadding = new Rect(); 464f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira mChipBackground.getPadding(backgroundPadding); 465f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira RectF dst = new RectF(width - iconWidth + backgroundPadding.left, 466f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira 0 + backgroundPadding.top, 467f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira width - backgroundPadding.right, 468f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira height - backgroundPadding.bottom); 469f9b3dcb83c869528b7679a5b181f936aa8fbecddMindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL); 4709024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 471c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4729024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 4739024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 475e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira paint.setColor(getContext().getResources().getColor(android.R.color.black)); 47697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira // Vertically center the text in the chip. 477379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 47897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira getTextYOffset((String)ellipsizedText, paint, height), paint); 479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 48597b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira private float getTextYOffset(String text, TextPaint paint, int height) { 48697b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira Rect bounds = new Rect(); 48797b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira paint.getTextBounds((String)text, 0, text.length(), bounds); 48897b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira int textHeight = bounds.bottom - bounds.top - (int)paint.descent(); 48997b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return height - ((height - textHeight) / 2); 49097b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira } 49197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira 4921e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 4931e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 4941e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 4951e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 4961e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 4981e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 499c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 5001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 5011e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 50277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 5031e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 5041e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 5051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 506e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 507c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 5081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 509045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 5101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 511c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 512c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 513c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 51577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 516c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 51877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 519c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 5202d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5212d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 5228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 523045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 524045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 5255519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 5265519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 5278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 528c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 5295519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 530416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 53197b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop()) 53297b873c0ba4f31b7e14abad8d873c35e66f7dc41Mindy Pereira + getDropDownVerticalOffset(); 533f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 534f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5358684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5368684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5378684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5388684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 540c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5411e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5422d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5432d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 544c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5454f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5464f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5474f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5481e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5494f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5511e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5521426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5541426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 555c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 556b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 557b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 55943876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 560045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 561d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 562b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 563b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 564b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 565b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 566b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 567b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 568b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 569b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 570b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 571b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 572b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 573b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 574b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 575b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 576bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 577bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 578bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 579bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 580bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 581bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 582bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 583bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 584bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 585bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 586bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 587c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 588c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 589c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 5907bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (width != 0 && height != 0 && mPendingChipsCount > 0) { 5917bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira postHandlePendingChips(); 5927bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 59377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 594416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView == null && !mTried) { 595416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 596416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 597416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 598416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 599416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 600416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 601416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 602416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mTried = true; 603416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 604c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 605c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 6110fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void handlePendingChips() { 612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount <= 0) { 613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira return; 614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 6157bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 6167bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 6177bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 6187bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 6197bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 6207bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 625a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 626a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 629a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 630a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 631a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 632a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 633a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 634a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 635a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 6360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 637a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 639a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira sanitizeSpannable(); 64022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0 641a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 642a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 643a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 644a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 645a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 646a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 647a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 648a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 649a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 65077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 651a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 652a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 653a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 654a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 65522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // to showing addresses for all of them. 656a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 65777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 65877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 659a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 660a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 6660fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6670fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void sanitizeSpannable() { 6680fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 6690fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientChip[] chips = getRecipients(); 6700fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 6710fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 67322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mMoreChip = getMoreChip(); 6740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 6750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 6760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 6770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = chips[chips.length - 1]; 6780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6790fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 6800fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 6810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 6820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 6830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 6840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 6850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 6860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 6870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 6890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 6950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 6960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 6981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 6991e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 7001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 7011e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 7021e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 703b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 7041e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 7050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 7060fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 7070fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7080fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 709d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 710a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira String destText = createDisplayText(entry); 711d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 712d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int textLength = destText.length() - 1; 713d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 714d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 715d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 716d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 717d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 718d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 719d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 720d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 721d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 722d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 723d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 724d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 725d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 72622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (mTemporaryRecipients == null) { 72722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(); 72822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 7296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 730d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 731d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 73277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 7330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 736d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 737d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 738d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 7401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 7416ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 742454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 743454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 7441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 7451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 7461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 7476ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 7486ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 7496ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 7506ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 7516ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 7521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7546ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 7556ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 756a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira String validatedToken = null; 7576ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 7586ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 759a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = mValidator.fixText(token).toString(); 760a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (!TextUtils.isEmpty(validatedToken)) { 761a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (validatedToken.contains(token)) { 762a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // protect against the case of a validator with a null domain, 763a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // which doesn't add a domain to the token 764a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken); 765a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira if (tokenized.length > 0) { 766a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = tokenized[0].getAddress(); 767a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } 768a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira } else { 769a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // We ran into a case where the token was invalid and removed 770a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // by the validator. In this case, just use the original token 771a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira // and let the user sort out the error chip. 772a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira validatedToken = null; 773490556a764a879cd0eaff358e90705cc1335c92eErik } 774d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7756ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 776454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 777a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira return RecipientEntry 778a9c5969299cf942278391a569bb6fdd1c4a8bb6bMindy Pereira .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token); 7791174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 7816ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 7826ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 7836ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 7846ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 7851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 7861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 7871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 7881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 7890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 7910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 793c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 794c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 795c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 797c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 798c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7998684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 8008684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 8018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 8028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 8038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 8048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 8058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8068684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 8078684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 8088684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 8098684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 810c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 811c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 812c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 813c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 814c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 8158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 8178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 818c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 81995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 82095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 82195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 82295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 82395d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 82495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 82595d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 8268684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 8278684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 8288684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 8298684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 8308684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 8318684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 8328684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 8338684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 83495d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 835c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 836c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 837c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 838c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 839c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 840d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 841c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 842c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 843e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 844e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 845e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 846e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 847e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 848e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 849c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 85095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 851e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 852e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 853e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 854e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 855e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 856e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 857e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 858e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 859e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 860e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 861e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 862c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 863c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 864c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 865c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 866e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 867e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 868e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 869e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 870e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 871e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 872e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 873e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 874e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 875045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 876045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 877045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 878045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 879045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 880045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 881045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 8828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 8834e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 884045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 8854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 886dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 887e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 888e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 889e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 890e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 892e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 893e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 8944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 895e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 896e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 897e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 898e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 899e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 900e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 905e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 9064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 907054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 9094e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 911001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (getAdapter().getCount() > 0 && enoughToFilter()) { 912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 913e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 914e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 915e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 916e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 917e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 918e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 919e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 920e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 9211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 922d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 923d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 924d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 9255ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira if (chipText != null) { 9265ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira editable.replace(start, end, chipText); 9275ac77e6db71c0bc45bcc40cc5cb721333dcb5857Mindy Pereira } 928d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 929d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 930f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 9314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 932d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 933d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 9344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 935d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 936d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 937f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira private void sanitizeBetween() { 938f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira // Find the last chip. 939f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip[] recips = this.getSortedRecipients(); 940f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips != null && recips.length > 0) { 941f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip last = recips[recips.length - 1]; 942f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira RecipientChip beforeLast = null; 943f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (recips.length > 1) { 944f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira beforeLast = recips[recips.length - 2]; 945f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 946f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int startLooking = 0; 947f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira int end = getSpannable().getSpanStart(last); 948f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (beforeLast != null) { 949f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking = getSpannable().getSpanEnd(beforeLast); 950f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (getText().charAt(startLooking) == ' ') { 951f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira startLooking++; 952f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 953f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 954f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira if (startLooking != end) { 955f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira getText().delete(startLooking, end); 956f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 957f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 958f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira } 959f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira 960e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 9611e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 9621e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 9631e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 9641e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 9651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 9661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 9671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 96805522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 9691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 970e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 971e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 972e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 97339f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira if (start == -1 || end == -1) { 97439f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira // This chip no longer exists in the field. 97539f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira dismissDropDown(); 97639f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira return; 97739f40bde5f9af410126c64d29c045fe8bf03c1bbMindy Pereira } 978e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 979e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 980e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 981e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 982e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 9833b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(text)) { 9843b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 9853b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 9863b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira CharSequence chipText = createChip(entry, false); 9873b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira editable.replace(start, getSelectionEnd(), chipText); 9883b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira } 989054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 99005522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 99105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 9928684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9938684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 9948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 9958684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 997c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 998b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 999b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1000b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1001b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1002b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1004c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 1006c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 1007c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1008c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1009c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 10102d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 10112d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 1012c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Spannable getSpannable() { 1013ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 1014c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1015c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1016b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 1017b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 1018b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1019b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1020b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 1021b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 1022b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1023b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1024c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1025c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 1026c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 1027c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 1029c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1031c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1032c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 1033c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 1034c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1035c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 1036c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 1037c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 1038c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 1039c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 1040c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 1041c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 1042c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1043c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1044c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 1045c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1046c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1047c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 1048c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 1050c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 1051c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 105236d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 1053c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1054c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 10568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 10578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 10588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 10598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 10608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 10618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 10628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1063c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1064c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 1065d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 1066d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 1067d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 1068d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 1069c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 1070d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 1071c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 10726caf49d02e7b2a719fc9fdf57e3f8e96dfdf082aMindy Pereira if (mSelectedChip == null) { 1073b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 1074b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 10750436c36bd529ea8ee1864a3017f9ac29d2311c08Mindy Pereira if (mCopyAddress == null && action == MotionEvent.ACTION_UP) { 1076c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1077c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1078c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1079c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1080c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1081c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1084b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 10868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 10878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1088b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1090b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1094416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 10955753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira } else if (mSelectedChip != null 10965753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 10975753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira chipWasSelected = true; 1098c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1099c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1106416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1107416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1108c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1109416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1110416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1111416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1112b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1113b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1114b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1115c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1116b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1117b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1118b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 1119e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 112077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1121416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1122b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1123368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1124e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira // Clear the checked item. 1125e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = -1; 1126b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1127b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1128b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1129b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1130b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1131b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1132b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1133b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1134b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1135b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1136b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1137b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1138b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1139e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira // Dismiss listener for alterns and single address popup. 1140e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira @Override 1141e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira public void onDismiss() { 1142e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(true); 1143e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira } 1144e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira 1145b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1146b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1147b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1148b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1149b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 11501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 11511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 11521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 11531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 11541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1155a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1156b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1157b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1158b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1159b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1161e9278b0f8b883a987a913c5d785455f5d0e646a9Mindy Pereira mCheckedItem = position; 1162b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1164c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1165c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1166c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1167c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1175c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1176c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1177c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1178c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11814fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 11824fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 11834fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1184c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1185c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 11864fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1187c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1188c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1189c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1190c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1191c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1192c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1193c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11944fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 11954fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 11964fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 11974fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 11984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 11994fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 12004fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1201c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1202c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1203c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1204c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1205c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1212c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1213c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1214c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12158659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira private String createDisplayText(RecipientEntry entry) { 12168659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String display = entry.getDisplayName(); 12178659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String address = entry.getDestination(); 12188659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) { 12198659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira display = null; 12208659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 1221a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira if (address != null) { 1222a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // Tokenize out the address in case the address already 1223a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira // contained the username as well. 1224a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira address = Rfc822Tokenizer.tokenize(address)[0].getAddress(); 1225a590df861d7a42cd2889ee96d0b9e51b47d28755Mindy Pereira } 12268659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira Rfc822Token token = new Rfc822Token(display, address, null); 12278659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = token.toString(); 12288659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String trimmedDisplayText = displayText.trim(); 12298659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira int index = trimmedDisplayText.indexOf(","); 12308659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira return index < trimmedDisplayText.length() - 1 ? (String) mTokenizer 12318659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira .terminateToken(displayText) : displayText; 12328659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira } 12338659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira 1234fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 12358659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira String displayText = createDisplayText(entry); 1236c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1237b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int textLength = displayText.length()-1; 1238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1241c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 12426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 12436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1244c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 12456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1246c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1250c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1251c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1252c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1253c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 12558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 12568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 12578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1258c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1259c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1260c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1261c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1262c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1263c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 12640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 12650fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 12661e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 12671e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 12681e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1269c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1270c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1271c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1272c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1273c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1274c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1275c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 12764221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 12774221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 12784221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 12794221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1280f6b6b0a26cdebf436dbee3eb4a7118081db7b4ccMindy Pereira sanitizeBetween(); 1281c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1282c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 12840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 12850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 12860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 12870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 12880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 12890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 12900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 12910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1292c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1293c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 12940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 12950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 12960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 12970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 12980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 12990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 13000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 13010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1302c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1303c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1304c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 13057a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 13067a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 13077a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 13087a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 13097a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1310c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1311c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1312c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1313c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 131483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 131583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 131683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 131783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 13186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private RecipientChip[] getSortedRecipients() { 13196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 13206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira .asList(getRecipients())); 13216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 13226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 13236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 13246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 13256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 13266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 13276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 13286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 13296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 13306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 13316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 13326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 13336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 13346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 13356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 13366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 13376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 13386f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 13396f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1340c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 1341c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 1342c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 134383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 13447a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 13457a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 13467a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 13477a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1348c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1349c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1350c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 13524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 13554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 13564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 13604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 13644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 13654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13688684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 13698684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 13704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 13714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 13724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 13734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 137522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 137622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira private ImageSpan getMoreChip() { 137722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(), 137822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira MoreImageSpan.class); 137922b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null; 138022b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 138122b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira 13828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1383045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1384045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1385045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 13868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 13870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createMoreChip() { 1388bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1389bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1390bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1391bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 13926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 13936f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 13946f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 13956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 13966f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 139783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 13980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 13990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 14004e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14016f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 140283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 14034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1404c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1405c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1406c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1407c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1408c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1409c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 14104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 14114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 14124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 141322b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira int adjustedHeight = height; 141422b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira Layout layout = getLayout(); 141522b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira if (layout != null) { 141622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira adjustedHeight -= layout.getLineDescent(0); 141722b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira } 141822b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint); 14194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14204e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 14214e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 14226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 14234e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1424368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 14254e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 14260fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 14270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 14280fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 14294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1430368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 14314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 14324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 14336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1434368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1435368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 14369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1437368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 14389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1439368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1440368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 14419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 14426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 14436f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 14446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 14456f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 144677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1447368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 14484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 144977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 145077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 145177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 14524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 145377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 14540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 14554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 14578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 14588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 14598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 14608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 14614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void removeMoreChip() { 14624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 14634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 14644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 14654e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 14664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 14674e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 14684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 146964077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira RecipientChip[] recipients = getRecipients(); 147064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // Start the search for tokens after the last currently visible 147164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // chip. 147264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira int end = span.getSpanEnd(recipients[recipients.length - 1]); 14734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 14744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 14756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 14760fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 14770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 14786f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 14796f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 148064077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // As we find the matching recipient for the remove spans, 148164077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // reduce the size of the string we need to search. 148264077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // That way, if there are duplicates, we always find the correct 148364077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira // recipient. 148464077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira chipStart = editable.toString().indexOf(token, end); 14856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // -1 for the space! 148664077e9710ac8e8df1b7c9828ea985cccb3d8446Mindy Pereira end = chipEnd = Math.min(editable.length(), chipStart + token.length()); 1487bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1488bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1489bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1490bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1491bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 14924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 14944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14954e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14964e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 14974e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1498c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1499b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1500b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1501b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1502b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1503b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1504b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1505b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1506b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1507b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1509c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public RecipientChip selectChip(RecipientChip currentChip) { 15111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 15121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 15131174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 15141174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 15151174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 15161174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 15171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 15182f5589283d93933751c20791ef42dc7eab87061aMindy Pereira return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1); 15191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1522b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1524b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1528b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1529b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1530fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 153283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1533b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 153483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 15358b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 153683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 15381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 15391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1540c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 15411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 15426ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 15451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 15461174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 15471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 15481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 15491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 15501174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 15511174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 15521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 15531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 15541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 15561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 15571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 15581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 15591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 15601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 15611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 15631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 15641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 15651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 15676ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 15681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1569fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1571fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1572c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 15731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 15741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 15751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 15761174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 15771174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 15781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 1579e0d95425cbadd7aaff5f8fdbb30ac40cb22c0aa4Mindy Pereira setEnabled(false); 15801174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 15811174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 15821174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 15831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 15841174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 15851174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 15861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 15871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 15881174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 15891174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 15911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 15921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 15931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 15941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 15951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 15961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1601b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void unselectChip(RecipientChip chip) { 1603b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1604b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1606c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1607b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 16085753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira Log.w(TAG, 16095753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira "The chip doesn't exist or may be a chip a user was editing"); 16105753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira setSelection(editable.length()); 16115753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira commitDefault(); 1612b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1613c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1614b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 16158b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 16168b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 16178b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 16188b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 16198b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 16208b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 16218b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1622c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1623b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1624b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1625b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1626b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1627c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1628b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1629c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1630b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1631b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether this chip contains the position passed in. 1632b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1633b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public boolean matchesChip(RecipientChip chip, int offset) { 1634b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1635b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1636b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1637b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return false; 1638c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1639b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return (offset >= start && offset <= end); 1640b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1641c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1642c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1643b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1644b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1645b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1646b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1647b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1648b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1649b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1650b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1651b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1652b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1653b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1654b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1655b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1656b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 16573656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1658b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1659b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1660b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1661b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void removeChip(RecipientChip chip) { 1662b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1663b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1664b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1665b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1666b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int toDelete = spanEnd; 1667b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1668b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1669b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1670b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1671c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1672b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Always remove trailing spaces when removing a chip. 16738b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1674b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira toDelete++; 1675c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1676b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1677b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira text.delete(spanStart, toDelete); 1678b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1679b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1680c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1681b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1682c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1683b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1684b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1685b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1686b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1687b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void replaceChip(RecipientChip chip, RecipientEntry entry) { 1688b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1689b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1690b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1691c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1692b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1693b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1694b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1695b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1696b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1697b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1698b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1699b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira editable.insert(0, chipText); 1700b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1701b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // There may be a space to replace with this chip's new associated 1702b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // space. Check for it. 1703b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int toReplace = end; 1704b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1705b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira && editable.charAt(toReplace) == ' ') { 1706b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira toReplace++; 1707b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1708b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira editable.replace(start, toReplace, chipText); 1709c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1710b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1711b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1712b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1713c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1714b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1715c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1716b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1717b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1718b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1719b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1720b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1721b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1722b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1723b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1724b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1725b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1726b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1727c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1728c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1729b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1730c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1731368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1732368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1733368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1734368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1735311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1736311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1737311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1738311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1739311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1740311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1741e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1742e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1743e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 17441e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 17451e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 17461e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 17471e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 17481e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 17491e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 17501e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 17511e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 17521e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 17531e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 17541e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 17551e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 17561e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 17571e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 17581e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 17591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 17601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1761368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1762e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1763e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 17645753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira // If the user is editing a chip, don't clear it. 17655753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira if (mSelectedChip != null 17665753143e03a6b8a61b2ffd6cbb04f1d54f406956Mindy Pereira && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) { 1767e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1768e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1769e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1770e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1771e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1772e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1773054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1774e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1775054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 177676ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1777054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1778054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1779054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1780054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1781054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1782054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1783e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1784e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1785e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1786e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1787e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1788e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1789e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1790e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1791e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 17923b646f13eb127b26d45ead7f819b943b2b01ce91Mindy Pereira if (!TextUtils.isEmpty(sub) && mValidator != null && mValidator.isValid(sub)) { 1793e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1794e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1795e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1796e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1797e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1798e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1799e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1800e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 18011174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1802e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1803e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1804e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1805e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 180622b680b63446e2b50f0f5e7d5307c7198387cebfMindy Pereira // Do nothing. 1807e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1808e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 180977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 181077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 181177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 181277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 181377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 181477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 181577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 181677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 181777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 181877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 181977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 182077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 182177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 182277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 182377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 182477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 182577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 182677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 182777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 182877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 18296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 183077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 183177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 183277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 183377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 183477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 183577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 183677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 183777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 18388659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 183977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 184077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 184177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 184277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 184377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 184477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 18451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 184677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 184777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 18481174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 18491174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 185077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 185177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 185277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 185377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 185477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 185577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 185677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 185777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 185877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 185977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 186077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 186177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 186277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 186377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 186477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 186577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 186677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 186777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 186877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 1869b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 1870b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira text.removeSpan(chip); 1871b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 18726f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 18736f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 1874b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 18756f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 187677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 187777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 187877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 187977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable editable = getText(); 188077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.clear(); 188177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.insert(0, text); 188277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 188377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 188477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 188577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 188677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 188777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 188877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 188977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 189077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 189177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 189277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 189377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 189477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 189577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 189677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 189777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 189877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 189977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 190077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 19018659fc7fa4a245c962f8bc29b351acc56d02dfaaMindy Pereira addresses[i] = createDisplayText(originalRecipients.get(i).getEntry()); 190277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 190377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 190477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 190577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 19061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 190777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 190877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 19091174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 19101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 191177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 191277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 191377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 191477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 191577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 191677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 191777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 191877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 191977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 192277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 192377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1924b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 19256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 19266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 19276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 19286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 19296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 19306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 19316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 19326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 19336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 19346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 19356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1936b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1937b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 1938b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1939b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1940b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1941b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1942b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 1943b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1944b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1945b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1946b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1947b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1948b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 1949b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 1950b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 1951b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1952b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 1953b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 1954b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1955b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 1956b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 1957b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 1958b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 1959b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1960b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1961b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1962b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 1963b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 1964b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 1965b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 1966b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 1967b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 1968b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 1969b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 1970b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 1971b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1972b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1973b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1974b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 1975b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1976b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1977b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1978b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1979b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1980b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 1981b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1982b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1983b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1984b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1985b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 1986b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1987b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1988b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1989b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1990b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1991b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 1992b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 1993b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1994b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1995b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1996b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 1997b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 1998b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 1999b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 2000b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 2001b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 2002b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 200377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 2004