RecipientEditTextView.java revision b67763f7bccadc3a34037d9d47c7f3107f639195
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; 362d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.text.Editable; 37572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.text.InputType; 38c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Layout; 39c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spannable; 40c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.SpannableString; 4177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport android.text.SpannableStringBuilder; 42c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.Spanned; 43c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextPaint; 44c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.TextUtils; 45c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereiraimport android.text.TextWatcher; 46c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.method.QwertyKeyListener; 47c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.text.style.ImageSpan; 480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Token; 490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereiraimport android.text.util.Rfc822Tokenizer; 502d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.util.AttributeSet; 51c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.util.Log; 524fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.ActionMode; 53a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport android.view.ActionMode.Callback; 54b8208f24b2768acf369ad58309031feac87ce79cMindy Pereiraimport android.view.GestureDetector; 55c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.KeyEvent; 56c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.view.LayoutInflater; 574fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.Menu; 584fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereiraimport android.view.MenuItem; 59c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.MotionEvent; 60c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.view.View; 61572fa15d4b847c2d890b972d4f69d4d2aad5ebd7Gilles Debunneimport android.view.View.OnClickListener; 62416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.view.ViewParent; 63c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView; 64c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.AdapterView.OnItemClickListener; 65cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereiraimport android.widget.Filterable; 66b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereiraimport android.widget.ListAdapter; 67c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport android.widget.ListPopupWindow; 68156467329e276c9bc90945bea916ce3ac4849574Mindy Pereiraimport android.widget.ListView; 692d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereiraimport android.widget.MultiAutoCompleteTextView; 70416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereiraimport android.widget.ScrollView; 71c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereiraimport android.widget.TextView; 72b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawa 73a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadlerimport java.util.ArrayList; 746f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Arrays; 75b5ebb8916a1bb178062a0ccb59e80de27ec1bb2dDaisuke Miyakawaimport java.util.Collection; 766f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Collections; 776f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereiraimport java.util.Comparator; 7877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereiraimport java.util.HashMap; 79c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.HashSet; 80c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereiraimport java.util.Set; 81c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 822d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira/** 832d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * RecipientEditTextView is an auto complete text view for use with applications 842d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira * that use the new Chips UI for addressing a message to recipients. 852d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira */ 86b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereirapublic class RecipientEditTextView extends MultiAutoCompleteTextView implements 87b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener, 88b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira GestureDetector.OnGestureListener, OnDismissListener, OnClickListener { 89c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 90c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private static final String TAG = "RecipientEditTextView"; 91c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 924e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // TODO: get correct number/ algorithm from with UX. 934e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private static final int CHIP_LIMIT = 2; 944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 95c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackground = null; 96c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 97c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipDelete = null; 98c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 99c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mChipPadding; 100c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Tokenizer mTokenizer; 102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 103c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Drawable mChipBackgroundPressed; 104c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip mSelectedChip; 106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int mAlternatesLayout; 108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private Bitmap mDefaultContactPhoto; 1101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 1114e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private ImageSpan mMoreChip; 1124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 113c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira private TextView mMoreItem; 1144e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 115054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira private final ArrayList<String> mPendingChips = new ArrayList<String>(); 116a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira 117e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipHeight; 118e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 119e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private float mChipFontSize; 120e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira 1218684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private Validator mValidator; 1228684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 123045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira private Drawable mInvalidChipBackground; 124045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 125156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private Handler mHandler; 126156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 127156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static int DISMISS = "dismiss".hashCode(); 128156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 129156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira private static final long DISMISS_DELAY = 300; 130156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira 131c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira private int mPendingChipsCount = 0; 132c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 13377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira private static int sSelectedTextColor = -1; 13477db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira 13505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_COMMA = ','; 13605522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 13705522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira private static final char COMMIT_CHAR_SEMICOLON = ';'; 138c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 139e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private static final char COMMIT_CHAR_SPACE = ' '; 140dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 141b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListPopupWindow mAlternatesPopup; 142b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListPopupWindow mAddressPopup; 1441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 14577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private ArrayList<RecipientChip> mTemporaryRecipients; 14677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 1470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private ArrayList<RecipientChip> mRemovedSpans; 1480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 149bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira private boolean mShouldShrink = true; 150bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 151b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Chip copy fields. 152b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private GestureDetector mGestureDetector; 153b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 154b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private Dialog mCopyDialog; 155b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 156b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private int mCopyViewRes; 157b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 158b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private String mCopyAddress; 159b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 160b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 161a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a 162a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler * selected chip. 163b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 164b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private OnItemClickListener mAlternatesListener; 165b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 166416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private int mCheckedItem; 167e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private TextWatcher mTextWatcher; 168e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 169416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private ScrollView mScrollView; 170416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 171416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private boolean mTried; 172416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 173e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private final Runnable mAddTextWatcher = new Runnable() { 174e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 175e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void run() { 176e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mTextWatcher == null) { 177e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira mTextWatcher = new RecipientTextWatcher(); 178e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira addTextChangedListener(mTextWatcher); 179e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 180e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 181e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira }; 182e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 18377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private IndividualReplacementTask mIndividualReplacements; 18477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 185a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private Runnable mHandlePendingChips = new Runnable() { 186a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 187a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira @Override 188a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira public void run() { 189a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira handlePendingChips(); 190a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 191a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 192a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira }; 193a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 1942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira public RecipientEditTextView(Context context, AttributeSet attrs) { 1952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira super(context, attrs); 19677db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira if (sSelectedTextColor == -1) { 19777db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira sSelectedTextColor = context.getResources().getColor(android.R.color.white); 19877db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira } 199b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup = new ListPopupWindow(context); 2001174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira mAddressPopup = new ListPopupWindow(context); 201b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesListener = new OnItemClickListener() { 202b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira @Override 203b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onItemClick(AdapterView<?> adapterView,View view, int position, 204b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira long rowId) { 205368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mAlternatesPopup.setOnItemClickListener(null); 206b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter()) 207b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira .getRecipientEntry(position)); 208b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Message delayed = Message.obtain(mHandler, DISMISS); 209368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira delayed.obj = mAlternatesPopup; 210b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mHandler.sendMessageDelayed(delayed, DISMISS_DELAY); 211b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearComposingText(); 212b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 213b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira }; 2146f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 215c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira setOnItemClickListener(this); 2164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira setCustomSelectionActionModeCallback(this); 217156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira mHandler = new Handler() { 218156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira @Override 219156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira public void handleMessage(Message msg) { 220156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira if (msg.what == DISMISS) { 221b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ((ListPopupWindow) msg.obj).dismiss(); 222156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira return; 223156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 224156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira super.handleMessage(msg); 225156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira } 226156467329e276c9bc90945bea916ce3ac4849574Mindy Pereira }; 2276ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mTextWatcher = new RecipientTextWatcher(); 2286ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira addTextChangedListener(mTextWatcher); 229b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector = new GestureDetector(context, this); 230ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 231cb041c294cd3fe41062f1b76d3bfc7d1cb1981d9Mindy Pereira 232ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik @Override 233ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik public <T extends ListAdapter & Filterable> void setAdapter(T adapter) { 234ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik super.setAdapter(adapter); 235ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik if (adapter == null) { 236ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return; 237ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik } 2384fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2394fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 2404fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira @Override 2414fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira public void onSelectionChanged(int start, int end) { 2424fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // When selection changes, see if it is inside the chips area. 2434fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If so, move the cursor back after the chips again. 24483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira Spannable span = getSpannable(); 24583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int textLength = getText().length(); 24683e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip[] chips = span.getSpans(start, textLength, RecipientChip.class); 24783e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (chips != null && chips.length > 0) { 2484fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (chips != null && chips.length > 0) { 2494fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // Grab the last chip and set the cursor to after it. 250b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(Math.min(span.getSpanEnd(chips[chips.length - 1]) + 1, textLength)); 2514fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 252d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 253d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira super.onSelectionChanged(start, end); 254d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 255d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 256c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira /** 257c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * Convenience method: Append the specified text slice to the TextView's 258c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * display buffer, upgrading it to BufferType.EDITABLE if it was 259c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * not already editable. Commas are excluded as they are added automatically 260c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira * by the view. 261c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira */ 262c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 263c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void append(CharSequence text, int start, int end) { 264001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // We don't care about watching text changes while appending. 265001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mTextWatcher != null) { 266001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira removeTextChangedListener(mTextWatcher); 267001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 268c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.append(text, start, end); 269c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) { 2704f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira final String displayString = (String) text; 27105522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA); 272c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (seperatorPos != 0 && !TextUtils.isEmpty(displayString) 273c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira && TextUtils.getTrimmedLength(displayString) > 0) { 274c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira mPendingChipsCount++; 275a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira mPendingChips.add((String)text); 276c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 277c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 278a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Put a message on the queue to make sure we ALWAYS handle pending chips. 279a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount > 0) { 280a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 281a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 2826ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira mHandler.post(mAddTextWatcher); 283c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 284c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 285d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira @Override 286d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira public void onFocusChanged(boolean hasFocus, int direction, Rect previous) { 287b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira super.onFocusChanged(hasFocus, direction, previous); 288d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!hasFocus) { 2894e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira shrink(); 2904fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } else { 2914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira expand(); 292416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira scrollLineIntoView(getLineCount()); 2934fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 2942d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 2952d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 296b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira @Override 297b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira public void performValidation() { 298b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira // Do nothing. Chips handles its own validation. 299b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira } 300b67763f7bccadc3a34037d9d47c7f3107f639195Mindy Pereira 3014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void shrink() { 3024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mSelectedChip != null) { 3034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira clearSelectedChip(); 3044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } else { 305001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // Reset any pending chips as they would have been handled 306001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // when the field lost focus. 307001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (mPendingChipsCount > 0) { 308a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira postHandlePendingChips(); 309001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 310001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira Editable editable = getText(); 311001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int end = getSelectionEnd(); 312001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 313001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 314001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if ((chips == null || chips.length == 0)) { 315001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 316001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // In the middle of chip; treat this as an edit 317001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira // and commit the whole token. 318001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (whatEnd != getSelectionEnd()) { 319001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira handleEdit(start, whatEnd); 320001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } else { 321001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira commitChip(start, end, editable); 322001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 323001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira } 324090139db34b366608b60e73f312833d84cf42259Mindy Pereira } 325001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira mHandler.post(mAddTextWatcher); 3264e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3270fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira createMoreChip(); 3284e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3294e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3304e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void expand() { 3314e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira removeMoreChip(); 3324e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setCursorVisible(true); 3334e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable text = getText(); 3344e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira setSelection(text != null && text.length() > 0 ? text.length() : 0); 33577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there are any temporary chips, try replacing them now that the user 33677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // has expanded the field. 33777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) { 33877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira new RecipientReplacementTask().execute(); 33977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mTemporaryRecipients = null; 34077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 3414e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 3424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 3431e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) { 344e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira paint.setTextSize(mChipFontSize); 345c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) { 346c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira Log.d(TAG, "Max width is negative: " + maxWidth); 347c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 348c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira return TextUtils.ellipsize(text, paint, maxWidth, 349c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira TextUtils.TruncateAt.END); 3501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 351c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 352e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 3531e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 3541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // autocomplete text entry area. Make sure to leave space for padding 3551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // on the sides. 356e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 3571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int deleteWidth = height; 3581e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira CharSequence ellipsizedText = ellipsizeText(contact.getDisplayName(), paint, 3591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(true) - deleteWidth); 3601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 3611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 3621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // tap a chip without difficulty. 3631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 3641e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 3651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + deleteWidth); 366c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 3671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Create the background of the chip. 3681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 3691e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Canvas canvas = new Canvas(tmpBitmap); 3701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackgroundPressed != null) { 3711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.setBounds(0, 0, width, height); 3721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipBackgroundPressed.draw(canvas); 37377db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(sSelectedTextColor); 3741e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Align the display text with where the user enters text. 3751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height 376e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira - Math.abs(height - mChipFontSize)/2, paint); 3771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Make the delete a square. 3781e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.setBounds(width - deleteWidth, 0, width, height); 3791e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira mChipDelete.draw(canvas); 3801e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 3811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 3821e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 3831e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 3841e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 385c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 386045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 387045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 388045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Get the background drawable for a RecipientChip. 389045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 390045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira public Drawable getChipBackground(RecipientEntry contact) { 391a501d9f79683be8d82c348ae50c97e2655d79cceMindy Pereira return (mValidator != null && mValidator.isValid(contact.getDestination())) ? 392045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira mChipBackground : mInvalidChipBackground; 393045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira } 394045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira 395e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) { 396c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Ellipsize the text so that it takes AT MOST the entire width of the 397c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // autocomplete text entry area. Make sure to leave space for padding 398c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on the sides. 399e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira int height = (int) mChipHeight; 4001e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int iconWidth = height; 4014221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira String displayText = 4024221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDisplayName()) ? contact.getDisplayName() : 4034221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira !TextUtils.isEmpty(contact.getDestination()) ? contact.getDestination() : ""; 4044221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence ellipsizedText = ellipsizeText(displayText, paint, 4051e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira calculateAvailableWidth(false) - iconWidth); 4061a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // Make sure there is a minimum chip width so the user can ALWAYS 4071a8bbbf9aa203287f53736a680f96b16d5c878a7Mindy Pereira // tap a chip without difficulty. 4081e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0, 4091e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira ellipsizedText.length())) 4101e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira + (mChipPadding * 2) + iconWidth); 411c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 412c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Create the background of the chip. 413c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 414c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Canvas canvas = new Canvas(tmpBitmap); 415045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable background = getChipBackground(contact); 416045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira if (background != null) { 417045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.setBounds(0, 0, width, height); 418045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira background.draw(canvas); 4191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4209024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't draw photos for recipients that have been typed in. 4211174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) { 4229024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira byte[] photoBytes = contact.getPhotoBytes(); 42390081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // There may not be a photo yet if anything but the first contact address 42490081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // was selected. 42590081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira if (photoBytes == null && contact.getPhotoThumbnailUri() != null) { 42690081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira // TODO: cache this in the recipient entry? 42790081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira ((BaseRecipientAdapter) getAdapter()).fetchPhoto(contact, contact 42890081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira .getPhotoThumbnailUri()); 42990081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira photoBytes = contact.getPhotoBytes(); 43090081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira } 43190081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira 4329024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Bitmap photo; 4339024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (photoBytes != null) { 4349024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length); 4359024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } else { 4369024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // TODO: can the scaled down default photo be cached? 4379024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira photo = mDefaultContactPhoto; 4389024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 4399024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Draw the photo on the left side. 4409024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira Matrix matrix = new Matrix(); 4419024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight()); 442379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira RectF dst = new RectF(width - iconWidth, 0, width, height); 4439024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER); 4449024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira canvas.drawBitmap(photo, matrix, paint); 445c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4469024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira // Don't leave any space for the icon. It isn't being drawn. 4479024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira iconWidth = 0; 448c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 4491e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4501e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira // Align the display text with where the user enters text. 451379c70a85c105ed2b003f0525fab2914eb79d1d1Mindy Pereira canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, 45290081ee88c7eb216ea22f426aa6856e310a867e1Mindy Pereira height - Math.abs(height - mChipFontSize) / 2, paint); 453c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 4541e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Log.w(TAG, "Unable to draw a background for the chips as it was never set"); 4551e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 4561e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return tmpBitmap; 4571e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 458c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4591e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 4601e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throws NullPointerException { 4611e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (mChipBackground == null) { 4621e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira throw new NullPointerException( 4631e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira "Unable to render any chips as setChipDimensions was not called."); 464c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 4651e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Layout layout = getLayout(); 466c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 4671e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira TextPaint paint = getPaint(); 4681e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira float defaultSize = paint.getTextSize(); 46977db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira int defaultColor = paint.getColor(); 4701e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira 4711e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira Bitmap tmpBitmap; 4721e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira if (pressed) { 473e6f595ab1f8409f23978baae564ad3cb9e0b5f41Mindy Pereira tmpBitmap = createSelectedChip(contact, paint, layout); 474c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 4751e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } else { 476045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira tmpBitmap = createUnselectedChip(contact, paint, layout); 4771e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira } 478c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 479c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Pass the full text, un-ellipsized, to the chip. 480c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 4811e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight()); 48277db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira RecipientChip recipientChip = new RecipientChip(result, contact, offset); 483c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Return text to the original size. 484c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira paint.setTextSize(defaultSize); 48577db539da02cf42742330f1969fd94bb94a61db8Mindy Pereira paint.setColor(defaultColor); 486c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return recipientChip; 4872d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 4882d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 4898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 490045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Calculate the bottom of the line the chip will be located on using: 491045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * 1) which line the chip appears on 4925519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 2) the height of a chip 4935519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira * 3) padding built into the edit text view 4948684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 495c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira private int calculateOffsetFromBottom(int line) { 4965519208c37aea65dab7edf555ab27844e12cdd71Mindy Pereira // Line offsets start at zero. 497416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira int actualLine = getLineCount() - (line + 1); 498b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira return -((actualLine * ((int)mChipHeight) + getPaddingBottom()) + getPaddingTop()); 499f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira } 500f566dee91901e44db63df3bf393afb1d43a36f78Mindy Pereira 5018684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 5028684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Get the max amount of space a chip can take up. The formula takes into 5038684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * account the width of the EditTextView, any view padding, and padding 5048684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * that will be added to the chip. 5058684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 506c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private float calculateAvailableWidth(boolean pressed) { 5071e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2); 5082d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 5092d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 510c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 5114f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * Set all chip dimensions and resources. This has to be done from the 5124f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * application as this is a static library. 5134f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param chipBackground 5141e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipBackgroundPressed 5154f7412c084ad344e94a320b51270ac6480a47a84Mindy Pereira * @param invalidChip 5161e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param chipDelete 5171e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param defaultContact 5181426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param moreResource 5191e9f1deb091a9552d746a69685e59a2bf48b6971Mindy Pereira * @param alternatesLayout 5201426d714da911130b643f7039590eceaa6420aa7Mindy Pereira * @param chipHeight 521c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * @param padding Padding around the text in a chip 522b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param chipFontSize 523b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira * @param copyViewRes 524c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 52543876560a507b0672cd2732c74bdbdec84ed3893Mindy Pereira public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 526045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira Drawable invalidChip, Drawable chipDelete, Bitmap defaultContact, int moreResource, 527d79e1a0e1a12944c6b9bae1dcfd5c602693281c0Mindy Pereira int alternatesLayout, float chipHeight, float padding, 528b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float chipFontSize, int copyViewRes) { 529b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackground = chipBackground; 530b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipBackgroundPressed = chipBackgroundPressed; 531b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipDelete = chipDelete; 532b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipPadding = (int) padding; 533b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mAlternatesLayout = alternatesLayout; 534b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mDefaultContactPhoto = defaultContact; 535b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(moreResource, null); 536b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipHeight = chipHeight; 537b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mChipFontSize = chipFontSize; 538b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mInvalidChipBackground = invalidChip; 539b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyViewRes = copyViewRes; 540b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 541b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 542bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira /** 543bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * Set whether to shrink the recipients field such that at most 544bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * one line of recipients chips are shown when the field loses 545bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * focus. By default, the number of displayed recipients will be 546bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * limited and a "more" chip will be shown when focus is lost. 547bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira * @param shrink 548bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira */ 549bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira public void setOnFocusListShrinkRecipients(boolean shrink) { 550bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira mShouldShrink = shrink; 551bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 552bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 553c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 554c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira public void onSizeChanged(int width, int height, int oldw, int oldh) { 555c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira super.onSizeChanged(width, height, oldw, oldh); 5567bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (width != 0 && height != 0 && mPendingChipsCount > 0) { 5577bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira postHandlePendingChips(); 5587bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 55977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Try to find the scroll view parent, if it exists. 560416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView == null && !mTried) { 561416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira ViewParent parent = getParent(); 562416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira while (parent != null && !(parent instanceof ScrollView)) { 563416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira parent = parent.getParent(); 564416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 565416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (parent != null) { 566416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mScrollView = (ScrollView) parent; 567416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 568416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira mTried = true; 569416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 570c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira } 571c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira 572a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira private void postHandlePendingChips() { 573a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.removeCallbacks(mHandlePendingChips); 574a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mHandler.post(mHandlePendingChips); 575a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 576a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira 5770fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void handlePendingChips() { 578a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mPendingChipsCount <= 0) { 579a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira return; 580a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 5817bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira if (getWidth() <= 0) { 5827bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // The widget has not been sized yet. 5837bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // This will be called as a result of onSizeChanged 5847bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira // at a later point. 5857bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira return; 5867bffb1a8fd8e479207e6ce1ea6c7622a079b8dc4Mindy Pereira } 587a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira synchronized (mPendingChips) { 588a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = new ArrayList<RecipientChip>(mPendingChipsCount); 589a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira Editable editable = getText(); 590a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Tokenize! 591a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira for (int i = 0; i < mPendingChips.size(); i++) { 592a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira String current = mPendingChips.get(i); 593a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenStart = editable.toString().indexOf(current); 594a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira int tokenEnd = tokenStart + current.length(); 595a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenStart >= 0) { 596a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // When we have a valid token, include it with the token 597a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to the left. 598a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (tokenEnd < editable.length() - 2 599a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) { 600a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira tokenEnd++; 601a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 602a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createReplacementChip(tokenStart, tokenEnd, editable); 6030fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 604a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount--; 6050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 606a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira sanitizeSpannable(); 607a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (mTemporaryRecipients != null 608a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) { 609a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) { 610a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira new RecipientReplacementTask().execute(); 611a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 612a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 613a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // Create the "more" chip 614a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements = new IndividualReplacementTask(); 615a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mIndividualReplacements.execute(new ArrayList<RecipientChip>( 616a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients.subList(0, CHIP_LIMIT))); 61777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 618a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira createMoreChip(); 619a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } 620a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira } else { 621a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // There are too many recipients to look up, so just fall back 622a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // to 623a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira // showing addresses for all of them. 624a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mTemporaryRecipients = null; 62577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira createMoreChip(); 62677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 627a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChipsCount = 0; 628a40ef1489593ee115d2734141cacaace585755b4Mindy Pereira mPendingChips.clear(); 6290fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6300fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6310fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6320fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6330fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Remove any characters after the last valid chip. 6340fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6350fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void sanitizeSpannable() { 6360fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // Find the last chip; eliminate any commit characters after it. 6370fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientChip[] chips = getRecipients(); 6380fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (chips != null && chips.length > 0) { 6390fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int end; 6400fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira ImageSpan lastSpan; 6410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (mMoreChip != null) { 6420fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = mMoreChip; 6430fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 6440fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira lastSpan = chips[chips.length - 1]; 6450fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira end = getSpannable().getSpanEnd(lastSpan); 6470fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Editable editable = getText(); 6480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int length = editable.length(); 6490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (length > end) { 6500fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // See what characters occur after that and eliminate them. 6510fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (Log.isLoggable(TAG, Log.DEBUG)) { 6520fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Log.d(TAG, "There were extra characters after the last tokenizable entry." 6530fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira + editable); 6540fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6550fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira editable.delete(end + 1, length); 6560fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6570fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6580fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 6600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira /** 6610fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * Create a chip that represents just the email address of a recipient. At some later 6620fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira * point, this chip will be attached to a real contact entry, if one exists. 6630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira */ 6640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) { 6651e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (alreadyHasChip(tokenStart, tokenEnd)) { 6661e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // There is already a chip present at this location. 6671e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Don't recreate it. 6681e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 6691e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 670b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira String token = editable.toString().substring(tokenStart, tokenEnd); 6711e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA); 6720fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (commitCharIndex == token.length() - 1) { 6730fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira token = token.substring(0, token.length() - 1); 6740fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 6750fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createTokenizedEntry(token); 676d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 677d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira String destText = entry.getDestination(); 678d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira destText = (String) mTokenizer.terminateToken(destText); 679d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Always leave a blank space at the end of a chip. 680d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int textLength = destText.length() - 1; 681d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira SpannableString chipText = new SpannableString(destText); 682d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int end = getSelectionEnd(); 683d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 684d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira RecipientChip chip = null; 685d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira try { 686d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chip = constructChipSpan(entry, start, false); 687d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 688d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } catch (NullPointerException e) { 689d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira Log.e(TAG, e.getMessage(), e); 690d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 6910fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 692d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(tokenStart, tokenEnd, chipText); 693d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira // Add this chip to the list of entries "to replace" 694d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (chip != null) { 6956f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 696d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira mTemporaryRecipients.add(chip); 697d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 69877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 6990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7000fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 7010fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createTokenizedEntry(String token) { 702d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (TextUtils.isEmpty(token)) { 703d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira return null; 704d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7050fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token); 7061174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira String display = null; 7076ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (isValid(token) && tokens != null && tokens.length > 0) { 708454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // If we can get a name from tokenizing, then generate an entry from 709454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // this. 7101174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira display = tokens[0].getName(); 7111174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (!TextUtils.isEmpty(display)) { 7121174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructGeneratedEntry(display, token); 7136ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } else { 7146ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira display = tokens[0].getAddress(); 7156ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira if (!TextUtils.isEmpty(display)) { 7166ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira return RecipientEntry.constructFakeEntry(display); 7176ccb1744887c175ced43491433b382f7c8f51e83Mindy Pereira } 7181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7191174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7206ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Unable to validate the token or to create a valid token from it. 7216ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Just create a chip the user can edit. 7226ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira if (mValidator != null && !mValidator.isValid(token)) { 7236ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira // Try fixing up the entry using the validator. 7246ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira token = mValidator.fixText(token).toString(); 725490556a764a879cd0eaff358e90705cc1335c92eErik if (!TextUtils.isEmpty(token)) { 726490556a764a879cd0eaff358e90705cc1335c92eErik // protect against the case of a validator with a null domain, 727490556a764a879cd0eaff358e90705cc1335c92eErik // which doesn't add a domain to the token 728490556a764a879cd0eaff358e90705cc1335c92eErik Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(token); 729490556a764a879cd0eaff358e90705cc1335c92eErik if (tokenized.length > 0) { 730490556a764a879cd0eaff358e90705cc1335c92eErik token = tokenized[0].getAddress(); 731490556a764a879cd0eaff358e90705cc1335c92eErik } 732d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 7336ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 734454eaf31f312fd2f2e6bc3d30bb050d65079a9a0Mindy Pereira // Otherwise, fallback to just creating an editable email address chip. 7351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return RecipientEntry.constructFakeEntry(token); 7361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 7371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 7386ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira private boolean isValid(String text) { 7396ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira return mValidator == null ? true : mValidator.isValid(text); 7406ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira } 7416ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira 7421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private String tokenizeAddress(String destination) { 7431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination); 7441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (tokens != null && tokens.length > 0) { 7451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return tokens[0].getAddress(); 7460fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return destination; 7480fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 7490fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 750c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira @Override 751c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void setTokenizer(Tokenizer tokenizer) { 752c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mTokenizer = tokenizer; 753c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.setTokenizer(mTokenizer); 754c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 755c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira @Override 7578684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira public void setValidator(Validator validator) { 7588684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira mValidator = validator; 7598684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira super.setValidator(validator); 7608684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira } 7618684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira 7628684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7638684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * We cannot use the default mechanism for replaceText. Instead, 7648684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * we override onItemClickListener so we can get all the associated 7658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information including display text, address, and id. 7668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 767c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 768c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void replaceText(CharSequence text) { 769c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 770c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 771c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 7728684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Dismiss any selected chips when the back key is pressed. 7748684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 775c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 77695d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira public boolean onKeyPreIme(int keyCode, KeyEvent event) { 77795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira if (keyCode == KeyEvent.KEYCODE_BACK) { 77895d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira clearSelectedChip(); 77995d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 78095d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira return super.onKeyPreIme(keyCode, event); 78195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira } 78295d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira 7838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 7848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor key presses in this view to see if the user types 7858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER. 7868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has contact matches and types 7878684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, create a chip from the topmost matching contact. 7888684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the user has entered text that has no contact matches and types 7898684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * a commit key, then create a chip from the text they have entered. 7908684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 79195d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira @Override 792c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyUp(int keyCode, KeyEvent event) { 793c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira switch (keyCode) { 794c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_ENTER: 795c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira case KeyEvent.KEYCODE_DPAD_CENTER: 796c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (event.hasNoModifiers()) { 797d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (commitDefault()) { 798c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 799c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 800e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 801e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 802e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 803e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira } else if (focusNext()) { 804e98f079e157d2872100174ba67beef8ebbc0cec0Mindy Pereira return true; 805e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 806c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 80795d81e62a0abb2f81624796f1fca9665cdb1a79eMindy Pereira break; 808e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira case KeyEvent.KEYCODE_TAB: 809e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (event.hasNoModifiers()) { 810e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (mSelectedChip != null) { 811e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira clearSelectedChip(); 812e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } else { 813e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira commitDefault(); 814e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 815e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (focusNext()) { 816e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 817e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 818e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 819c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 820c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyUp(keyCode, event); 821c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 822c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 823e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira private boolean focusNext() { 824e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira View next = focusSearch(View.FOCUS_DOWN); 825e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira if (next != null) { 826e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira next.requestFocus(); 827e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return true; 828e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 829e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira return false; 830e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira } 831e24cb318a0e07fce3db99e1ef6234a196f256befMindy Pereira 832045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira /** 833045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create a chip from the default selection. If the popup is showing, the 834045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * default is the first item in the popup suggestions list. Otherwise, it is 835045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * whatever the user had typed in. End represents where the the tokenizer 836045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * should search for a token to turn into a chip. 837045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * @return If a chip was created from a real contact. 838045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira */ 8398684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira private boolean commitDefault() { 8404e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 841045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira int end = getSelectionEnd(); 8424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int start = mTokenizer.findTokenStart(editable, end); 843dfa1d391014fac824be4ba48582f160ffcac7303Mindy Pereira 844e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 845e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int whatEnd = mTokenizer.findTokenEnd(getText(), start); 846e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // In the middle of chip; treat this as an edit 847e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit the whole token. 848e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (whatEnd != getSelectionEnd()) { 849e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira handleEdit(start, whatEnd); 850e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 8514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 852e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return commitChip(start, end , editable); 853e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 854e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return false; 855e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 856e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 857e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void commitByCharacter() { 858e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 859e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int end = getSelectionEnd(); 860e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int start = mTokenizer.findTokenStart(editable, end); 861e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (shouldCreateChip(start, end)) { 862e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitChip(start, end, editable); 8634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 864054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira setSelection(getText().length()); 865e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 8664e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 867e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean commitChip(int start, int end, Editable editable) { 868001f6063c1a2f447bb4dc90a31d2fd0a745c7fc1Mindy Pereira if (getAdapter().getCount() > 0 && enoughToFilter()) { 869e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // choose the first entry. 870e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira submitItemAtPosition(0); 871e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira dismissDropDown(); 872e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return true; 873e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else { 874e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenEnd = mTokenizer.findTokenEnd(editable, start); 875e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = editable.toString().substring(start, tokenEnd).trim(); 876e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearComposingText(); 877e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (text != null && text.length() > 0 && !text.equals(" ")) { 8781174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientEntry entry = createTokenizedEntry(text); 879d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira if (entry != null) { 880d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 881d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira CharSequence chipText = createChip(entry, false); 882d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira editable.replace(start, end, chipText); 883d58667fc7a460b8b56ccf9f0c35b92a2a9e0419cMindy Pereira } 884d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira dismissDropDown(); 8854e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return true; 886d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 887d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 8884e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 889d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 890d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 891e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private boolean shouldCreateChip(int start, int end) { 8921e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return hasFocus() && enoughToFilter() && !alreadyHasChip(start, end); 8931e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 8941e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira 8951e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira private boolean alreadyHasChip(int start, int end) { 8961e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class); 8971e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if ((chips == null || chips.length == 0)) { 8981e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return false; 89905522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 9001e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return true; 901e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 902e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 903e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private void handleEdit(int start, int end) { 904e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // This is in the middle of a chip, so select out the whole chip 905e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // and commit it. 906e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira Editable editable = getText(); 907e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(end); 908e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString().substring(start, end); 909e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 910e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 911e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira CharSequence chipText = createChip(entry, false); 912e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira editable.replace(start, getSelectionEnd(), chipText); 913054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira dismissDropDown(); 91405522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira } 91505522aeae1948f776e618f69d34d4a5e48a0471cMindy Pereira 9168684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9178684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If there is a selected chip, delegate the key events 9188684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * to the selected chip. 9198684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 920c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 921c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onKeyDown(int keyCode, KeyEvent event) { 922b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) { 923b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 924b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 925b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 926b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(mSelectedChip); 927c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 928c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 929c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 930c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return true; 931c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 932c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 933c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return super.onKeyDown(keyCode, event); 9342d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira } 9352d4ee907769ccfc94dc315e932ff235198958c69Mindy Pereira 936c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private Spannable getSpannable() { 937ed58ebd3f54ae5e73129f7f12c8342f177ed1fc3Erik return getText(); 938c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 939c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 940b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipStart(RecipientChip chip) { 941b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanStart(chip); 942b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 943b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 944b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private int getChipEnd(RecipientChip chip) { 945b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return getSpannable().getSpanEnd(chip); 946b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 947b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 948c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 949c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * Instead of filtering on the entire contents of the edit box, 950c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * this subclass method filters on the range from 951c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 952c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * if the length of that range meets or exceeds {@link #getThreshold} 953c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira * and makes sure that the range is not already a Chip. 954c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 955c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 956c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira protected void performFiltering(CharSequence text, int keyCode) { 957c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (enoughToFilter()) { 958c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 959c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(text, end); 960c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // If this is a RecipientChip, don't filter 961c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // on its contents. 962c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spannable span = getSpannable(); 963c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 964c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (chips != null && chips.length > 0) { 965c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return; 966c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 967c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 968c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira super.performFiltering(text, keyCode); 969c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 970c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 971c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void clearSelectedChip() { 972c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null) { 973b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira unselectChip(mSelectedChip); 974c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira mSelectedChip = null; 975c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 97636d720eb2b7a8d3466d221944a00b8bae7de8e9cMindy Pereira setCursorVisible(true); 977c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 978c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 9798684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 9808684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Monitor touch events in the RecipientEditTextView. 9818684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If the view does not have focus, any tap on the view 9828684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * will just focus the view. If the view has focus, determine 9838684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * if the touch target is a recipient chip. If it is and the chip 9848684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * is not selected, select it and clear any other selected chips. 9858684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * If it isn't, then select that chip. 9868684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 987c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 988c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public boolean onTouchEvent(MotionEvent event) { 989d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira if (!isFocused()) { 990d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira // Ignore any chip taps until this view is focused. 991d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira return super.onTouchEvent(event); 992d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira } 993d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira 994c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean handled = super.onTouchEvent(event); 995d9b57273c1f5c3bcd94e662136446cd6fd465ebcMindy Pereira int action = event.getAction(); 996c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira boolean chipWasSelected = false; 997b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (action == MotionEvent.ACTION_DOWN && mSelectedChip == null) { 998b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mGestureDetector.onTouchEvent(event); 999b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1000b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mCopyDialog == null && action == MotionEvent.ACTION_UP) { 1001c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float x = event.getX(); 1002c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira float y = event.getY(); 1003c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1004c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip currentChip = findChip(offset); 1005c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (currentChip != null) { 1006c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP) { 1007c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (mSelectedChip != null && mSelectedChip != currentChip) { 1008c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1009b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1010c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else if (mSelectedChip == null) { 10118684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // Selection may have moved due to the tap event, 10128684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // but make sure we correctly reset selection to the 10138684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira // end so that any unfinished chips are committed. 10148684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira setSelection(getText().length()); 10158684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira commitDefault(); 1016b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = selectChip(currentChip); 1017c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1018b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira onClick(mSelectedChip, offset, x, y); 1019c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1020c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1021c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira chipWasSelected = true; 1022416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira handled = true; 1023c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1024c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1025c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 1026c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearSelectedChip(); 1027c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1028c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return handled; 1029c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1030c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1031416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira private void scrollLineIntoView(int line) { 1032416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira if (mScrollView != null) { 1033c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira mScrollView.scrollBy(0, calculateOffsetFromBottom(line)); 1034416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1035416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira } 1036416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira 1037b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup, 1038b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int width, Context context) { 1039b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 1040c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira int bottom = calculateOffsetFromBottom(line); 1041b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Align the alternates popup with the left side of the View, 1042b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // regardless of the position of the chip tapped. 1043b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setWidth(width); 104477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira alternatesPopup.setAnchorView(this); 1045416e1307e57a36650bd15a06bb0ac2915a195a2eMindy Pereira alternatesPopup.setVerticalOffset(bottom); 1046b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.setAdapter(createAlternatesAdapter(currentChip)); 1047368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira alternatesPopup.setOnItemClickListener(mAlternatesListener); 1048b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira alternatesPopup.show(); 1049b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = alternatesPopup.getListView(); 1050b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1051b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Checked item would be -1 if the adapter has not 1052b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // loaded the view that should be checked yet. The 1053b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // variable will be set correctly when onCheckedItemChanged 1054b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // is called in a separate thread. 1055b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mCheckedItem != -1) { 1056b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(mCheckedItem, true); 1057b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = -1; 1058b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1059b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1060b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1061b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private ListAdapter createAlternatesAdapter(RecipientChip chip) { 1062b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(), 1063b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesLayout, this); 1064b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1065b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 10661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) { 10671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip 10681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getEntry()); 10691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 10701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1071a0751874f5bbdb39f2024c69a5024df60f0344d9Andy Stadler @Override 1072b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onCheckedItemChanged(int position) { 1073b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira ListView listView = mAlternatesPopup.getListView(); 1074b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (listView != null && listView.getCheckedItemCount() == 0) { 1075b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira listView.setItemChecked(position, true); 1076b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1077b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mCheckedItem = position; 1078b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1079b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1080b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira 1081c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // TODO: This algorithm will need a lot of tweaking after more people have used 1082c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 1083c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // what comes before the finger. 1084c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private int putOffsetInRange(int o) { 1085c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int offset = o; 1086c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable text = getText(); 1087c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int length = text.length(); 1088c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Remove whitespace from end to find "real end" 1089c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int realLength = length; 1090c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = length - 1; i >= 0; i--) { 1091c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira if (text.charAt(i) == ' ') { 1092c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira realLength--; 1093c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } else { 1094c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira break; 1095c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1096c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1097c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 10984fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // If the offset is beyond or at the end of the text, 10994fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira // leave it alone. 11004fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (offset >= realLength) { 1101c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1102c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 11034fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira Editable editable = getText(); 1104c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 1105c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Keep walking backward! 1106c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira offset--; 1107c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1108c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return offset; 1109c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1110c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11114fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira private int findText(Editable text, int offset) { 11124fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira if (text.charAt(offset) != ' ') { 11134fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return offset; 11144fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 11154fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira return -1; 11164fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira } 11174fef2782b06043dbf73c896c4d1962be0b8dcd50Mindy Pereira 1118c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private RecipientChip findChip(int offset) { 1119c4ad472c11324428ac9c75bcf7d52263ff4699f1Mindy Pereira RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 1120c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Find the chip that contains this offset. 1121c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira for (int i = 0; i < chips.length; i++) { 1122c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira RecipientChip chip = chips[i]; 1123b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1124b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1125b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (offset >= start && offset <= end) { 1126c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chip; 1127c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1128c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1129c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1130c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1131c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1132fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira private CharSequence createChip(RecipientEntry entry, boolean pressed) { 1133c72429f17e12ec39e61a7efdb7312b7f5d8a5cbcMindy Pereira String displayText = entry.getDestination(); 11344f5f0328efbd5f72e30adf08ba7d89a66b4659ceMindy Pereira displayText = (String) mTokenizer.terminateToken(displayText); 1135c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira // Always leave a blank space at the end of a chip. 1136b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int textLength = displayText.length()-1; 1137c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira SpannableString chipText = new SpannableString(displayText); 1138c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1139c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1140c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira try { 11416f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip chip = constructChipSpan(entry, start, pressed); 11426f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipText.setSpan(chip, 0, textLength, 1143c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 11446f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chip.setOriginalText(chipText.toString()); 1145c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } catch (NullPointerException e) { 1146c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Log.e(TAG, e.getMessage(), e); 1147c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return null; 1148c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1149c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1150c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return chipText; 1151c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1152c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11538684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 11548684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * When an item in the suggestions list has been clicked, create a chip from the 11558684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * contact information of the selected item. 11568684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 1157c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira @Override 1158c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1159c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira submitItemAtPosition(position); 1160c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1161c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1162c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira private void submitItemAtPosition(int position) { 11630fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira RecipientEntry entry = createValidatedEntry( 11640fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira (RecipientEntry)getAdapter().getItem(position)); 11651e85502fdc04a44f76ffa9904be9ab6ab80292ceErik if (entry == null) { 11661e85502fdc04a44f76ffa9904be9ab6ab80292ceErik return; 11671e85502fdc04a44f76ffa9904be9ab6ab80292ceErik } 1168c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira clearComposingText(); 1169c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1170c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int end = getSelectionEnd(); 1171c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira int start = mTokenizer.findTokenStart(getText(), end); 1172c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1173c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira Editable editable = getText(); 1174c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 11754221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira CharSequence chip = createChip(entry, false); 11764221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira if (chip != null) { 11774221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira editable.replace(start, end, chip); 11784221a1d7cab69fd886c03abf53f2250025367f5eMindy Pereira } 1179c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1180c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 11810fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private RecipientEntry createValidatedEntry(RecipientEntry item) { 11820fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira if (item == null) { 11830fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return null; 11840fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 11850fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira final RecipientEntry entry; 11860fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // If the display name and the address are the same, or if this is a 11870fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // valid contact, but the destination is invalid, then make this a fake 11880fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira // recipient that is editable. 11890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String destination = item.getDestination(); 1190c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira if (TextUtils.isEmpty(item.getDisplayName()) 1191c1564e400a60414db1eccbdf6de3913a3e1a8504Mindy Pereira || TextUtils.equals(item.getDisplayName(), destination) 11920fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira || (mValidator != null && !mValidator.isValid(destination))) { 11930fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = RecipientEntry.constructFakeEntry(destination); 11940fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } else { 11950fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira entry = item; 11960fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 11970fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return entry; 11980fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira } 11990fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira 1200c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of contact Id for each chip inside this View. */ 1201c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getContactIds() { 1202c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 12037a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira RecipientChip[] chips = getRecipients(); 12047a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 12057a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 12067a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getContactId()); 12077a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1208c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1209c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1210c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1211c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 121283e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira private RecipientChip[] getRecipients() { 121383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira return getSpannable().getSpans(0, getText().length(), RecipientChip.class); 121483e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 121583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira 12166f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private RecipientChip[] getSortedRecipients() { 12176f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays 12186f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira .asList(getRecipients())); 12196f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira final Spannable spannable = getSpannable(); 12206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Collections.sort(recipientsList, new Comparator<RecipientChip>() { 12216f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 12226f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira @Override 12236f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public int compare(RecipientChip first, RecipientChip second) { 12246f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int firstStart = spannable.getSpanStart(first); 12256f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int secondStart = spannable.getSpanStart(second); 12266f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (firstStart < secondStart) { 12276f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return -1; 12286f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else if (firstStart > secondStart) { 12296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 1; 12306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } else { 12316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return 0; 12326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12336f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12346f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira }); 12356f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira return recipientsList.toArray(new RecipientChip[recipientsList.size()]); 12366f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12376f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1238c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** Returns a collection of data Id for each chip inside this View. May be null. */ 1239c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /* package */ Collection<Long> getDataIds() { 1240c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira final Set<Long> result = new HashSet<Long>(); 124183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira RecipientChip [] chips = getRecipients(); 12427a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira if (chips != null) { 12437a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira for (RecipientChip chip : chips) { 12447a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira result.add(chip.getDataId()); 12457a424984849627457aa27ac19c0d23cbe0887c85Mindy Pereira } 1246c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1247c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira return result; 1248c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1249c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 12504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 12534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 12544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public void onDestroyActionMode(ActionMode mode) { 12584e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12594e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12604e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12614e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 12624e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 12634e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12644e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12658684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 12668684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * No chips are selectable. 12678684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 12684e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira @Override 12694e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira public boolean onCreateActionMode(ActionMode mode, Menu menu) { 12704e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira return false; 12714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 12738684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 1274045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * Create the more chip. The more chip is text that replaces any chips that 1275045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * do not fit in the pre-defined available space when the 1276045e80b59ef5e9a709b6e5843d2301a02e0872f2Mindy Pereira * RecipientEditTextView loses focus. 12778684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 12780fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira private void createMoreChip() { 1279bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira if (!mShouldShrink) { 1280bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira return; 1281bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira } 1282bfa63c7c85405200eb6a049c7209657440a4b1fdMindy Pereira 12836f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class); 12846f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (tempMore.length > 0) { 12856f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira getSpannable().removeSpan(tempMore[0]); 12866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 12876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] recipients = getSortedRecipients(); 128883e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (recipients == null || recipients.length <= CHIP_LIMIT) { 12890fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 12900fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 12914e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 12926f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Spannable spannable = getSpannable(); 129383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira int numRecipients = recipients.length; 12944e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int overage = numRecipients - CHIP_LIMIT; 1295c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira String moreText = String.format(mMoreItem.getText().toString(), overage); 1296c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira TextPaint morePaint = new TextPaint(getPaint()); 1297c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setTextSize(mMoreItem.getTextSize()); 1298c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint.setColor(mMoreItem.getCurrentTextColor()); 1299c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft() 1300c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira + mMoreItem.getPaddingRight(); 13014e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int height = getLineHeight(); 13024e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 13034e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Canvas canvas = new Canvas(drawable); 13044e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira canvas.drawText(moreText, 0, moreText.length(), 0, height - getLayout().getLineDescent(0), 1305c0f97e4f56bc35d99d181c5f604b26fbb89c113fMindy Pereira morePaint); 13064e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13074e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Drawable result = new BitmapDrawable(getResources(), drawable); 13084e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira result.setBounds(0, 0, width, height); 13096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira MoreImageSpan moreSpan = new MoreImageSpan(result); 13104e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Remove the overage chips. 1311368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (recipients == null || recipients.length == 0) { 13124e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Log.w(TAG, 13130fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira "We have recipients. Tt should not be possible to have zero RecipientChips."); 13140fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = null; 13150fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira return; 13164e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 1317368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans = new ArrayList<RecipientChip>(); 13184e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceStart = 0; 13194e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira int totalReplaceEnd = 0; 13206f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira Editable text = getText(); 1321368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira for (int i = numRecipients - overage; i < recipients.length; i++) { 1322368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira mRemovedSpans.add(recipients[i]); 13239024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira if (i == numRecipients - overage) { 1324368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceStart = spannable.getSpanStart(recipients[i]); 13259024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 1326368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (i == recipients.length - 1) { 1327368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira totalReplaceEnd = spannable.getSpanEnd(recipients[i]); 13289024e5c88fde2f878eea4bca6923ad57a3f0cfe0Mindy Pereira } 13296f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) { 13306f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanStart = spannable.getSpanStart(recipients[i]); 13316f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int spanEnd = spannable.getSpanEnd(recipients[i]); 13326f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd)); 133377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1334368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira spannable.removeSpan(recipients[i]); 13354e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 133677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int end = Math.max(totalReplaceStart, totalReplaceEnd); 133777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start = Math.min(totalReplaceStart, totalReplaceEnd); 133877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableString chipText = new SpannableString(text.subSequence(start, end)); 13394e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 134077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira text.replace(start, end, chipText); 13410fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira mMoreChip = moreSpan; 13424e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13434e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 13448684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira /** 13458684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * Replace the more chip, if it exists, with all of the recipient chips it had 13468684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira * replaced when the RecipientEditTextView gains focus. 13478684974e4befb4c9dcc21c995c4ff3af7103ab10Mindy Pereira */ 13484e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira private void removeMoreChip() { 13494e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mMoreChip != null) { 13504e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Spannable span = getSpannable(); 13514e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira span.removeSpan(mMoreChip); 13524e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mMoreChip = null; 13534e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Re-add the spans that were removed. 13544e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira if (mRemovedSpans != null && mRemovedSpans.size() > 0) { 13554e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira // Recreate each removed span. 13564e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira Editable editable = getText(); 13574e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira for (RecipientChip chip : mRemovedSpans) { 13586f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira int chipStart; 13590fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira int chipEnd; 13600fc929e5fb977fe028d7db04a3ad57e74f42e654Mindy Pereira String token; 13616f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // Need to find the location of the chip, again. 13626f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira token = (String) chip.getOriginalText(); 13636f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipStart = editable.toString().indexOf(token); 13646f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira // -1 for the space! 13656f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira chipEnd = Math.min(editable.length(), chipStart + token.length()); 1366bbf141b605f92729864984229e6e1062d9a462beMindy Pereira // Only set the span if we found a matching token. 1367bbf141b605f92729864984229e6e1062d9a462beMindy Pereira if (chipStart != -1) { 1368bbf141b605f92729864984229e6e1062d9a462beMindy Pereira editable.setSpan(chip, chipStart, chipEnd, 1369bbf141b605f92729864984229e6e1062d9a462beMindy Pereira Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1370bbf141b605f92729864984229e6e1062d9a462beMindy Pereira } 13714e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13724e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira mRemovedSpans.clear(); 13734e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13744e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13754e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira } 13764e1f6098a731be3d0be1f9e02a1fa2677e4de187Mindy Pereira 1377c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira /** 1378b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Show specified chip as selected. If the RecipientChip is just an email address, 1379b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * selecting the chip will take the contents of the chip and place it at 1380b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the end of the RecipientEditTextView for inline editing. If the 1381b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * RecipientChip is a complete contact, then selecting the chip 1382b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * will change the background color of the chip, show the delete icon, 1383b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * and a popup window with the address in use highlighted and any other 1384b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * alternate addresses for the contact. 1385b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @param currentChip Chip to select. 1386b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return A RecipientChip in the selected state or null if the chip 1387b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * just contained an email address. 1388c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira */ 1389b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public RecipientChip selectChip(RecipientChip currentChip) { 13901174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) { 13911174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira CharSequence text = currentChip.getValue(); 13921174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Editable editable = getText(); 13931174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira removeChip(currentChip); 13941174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.append(text); 13951174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setCursorVisible(true); 13961174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira setSelection(editable.length()); 13971174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 13981174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) { 1399b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(currentChip); 1400b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(currentChip); 1401b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(currentChip); 1402b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira RecipientChip newChip; 1403b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira try { 1404b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 1405b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } catch (NullPointerException e) { 1406b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, e.getMessage(), e); 1407b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return null; 1408b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1409fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira Editable editable = getText(); 1410b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 141183e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira if (start == -1 || end == -1) { 1412b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 141383e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } else { 14148b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 141583e4ae382ffbec673b16c85c35e3cfe19ada98dcMindy Pereira } 1416b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira newChip.setSelected(true); 14171174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 14181174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 1419c6e6141037bf299cabf4a1ba6b3664f5bc426bd0Mindy Pereira } 14201174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAddress(newChip, mAddressPopup, getWidth(), getContext()); 14216ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 1422b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return newChip; 1423b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 14241174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int start = getChipStart(currentChip); 14251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int end = getChipEnd(currentChip); 14261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira getSpannable().removeSpan(currentChip); 14271174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira RecipientChip newChip; 14281174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira try { 14291174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip = constructChipSpan(currentChip.getEntry(), start, true); 14301174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } catch (NullPointerException e) { 14311174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.e(TAG, e.getMessage(), e); 14321174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return null; 14331174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 1434b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 14351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 14361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (start == -1 || end == -1) { 14371174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira Log.d(TAG, "The chip being selected no longer exists but should."); 14381174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } else { 14391174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 14401174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14411174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira newChip.setSelected(true); 14421174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) { 14431174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip))); 14441174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14451174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira showAlternates(newChip, mAlternatesPopup, getWidth(), getContext()); 14466ebf62290f81d267b85f0237a49daa4c9b3846efMindy Pereira setCursorVisible(false); 14471174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira return newChip; 1448fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira } 1449b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1450fab0afdc6742dcba55cfbe802cd143434d48f413Mindy Pereira 1451c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 14521174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup, 14531174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int width, Context context) { 14541174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int line = getLayout().getLineForOffset(getChipStart(currentChip)); 14551174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira int bottom = calculateOffsetFromBottom(line); 14561174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Align the alternates popup with the left side of the View, 14571174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // regardless of the position of the chip tapped. 14581174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setWidth(width); 14591174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAnchorView(this); 14601174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setVerticalOffset(bottom); 14611174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setAdapter(createSingleAddressAdapter(currentChip)); 14621174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.setOnItemClickListener(new OnItemClickListener() { 14631174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira @Override 14641174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 14651174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira unselectChip(currentChip); 14661174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.dismiss(); 14671174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14681174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira }); 14691174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira popup.show(); 14701174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira ListView listView = popup.getListView(); 14711174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 14721174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira listView.setItemChecked(0, true); 14731174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira } 14741174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira 1475b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1476b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove selection from this chip. Unselecting a RecipientChip will render 1477b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * the chip without a delete icon and with an unfocused background. This 1478b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * is called when the RecipientChip no longer has focus. 1479b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1480b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void unselectChip(RecipientChip chip) { 1481b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1482b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1483b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1484c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira mSelectedChip = null; 1485b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1486b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, "The chip being unselected no longer exists but should."); 1487b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1488c57ee19c891696a126fe8b6dec74aa4f6a00c6a5Mindy Pereira getSpannable().removeSpan(chip); 1489b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira QwertyKeyListener.markAsReplaced(editable, start, end, ""); 14908b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.removeSpan(chip); 14918b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira try { 14928b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end, 14938b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 14948b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } catch (NullPointerException e) { 14958b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira Log.e(TAG, e.getMessage(), e); 14968b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira } 1497c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1498b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1499b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setSelection(editable.length()); 1500b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) { 1501b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mAlternatesPopup.dismiss(); 1502c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1503b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1504c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1505c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1506b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1507b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether this chip contains the position passed in. 1508b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1509b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public boolean matchesChip(RecipientChip chip, int offset) { 1510b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1511b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1512b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1513b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return false; 1514c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1515b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return (offset >= start && offset <= end); 1516b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1517c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1518c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1519b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1520b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Return whether a touch event was inside the delete target of 1521b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * a selected chip. It is in the delete target if: 1522b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 1) the x and y points of the event are within the 1523b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * delete assset. 1524b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * 2) the point tapped would have caused a cursor to appear 1525b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * right after the selected chip. 1526b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * @return boolean 1527b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1528b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private boolean isInDelete(RecipientChip chip, int offset, float x, float y) { 1529b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Figure out the bounds of this chip and whether or not 1530b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // the user clicked in the X portion. 1531b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira return chip.isSelected() && offset == getChipEnd(chip); 1532b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 15333656f7e97c58dc8443132d2d8297629b6a04cce7Mindy Pereira 1534b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1535b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Remove the chip and any text associated with it from the RecipientEditTextView. 1536b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1537b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira private void removeChip(RecipientChip chip) { 1538b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Spannable spannable = getSpannable(); 1539b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanStart = spannable.getSpanStart(chip); 1540b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int spanEnd = spannable.getSpanEnd(chip); 1541b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable text = getText(); 1542b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int toDelete = spanEnd; 1543b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1544b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Clear that there is a selected chip before updating any text. 1545b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1546b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1547c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1548b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira // Always remove trailing spaces when removing a chip. 15498b308552260807ba6611a4ab3bb23f0211608767Mindy Pereira while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') { 1550b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira toDelete++; 1551c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1552b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira spannable.removeSpan(chip); 1553b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira text.delete(spanStart, toDelete); 1554b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1555b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1556c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1557b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1558c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1559b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1560b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Replace this currently selected chip with a new chip 1561b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * that uses the contact data provided. 1562b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1563b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void replaceChip(RecipientChip chip, RecipientEntry entry) { 1564b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira boolean wasSelected = chip == mSelectedChip; 1565b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1566b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira mSelectedChip = null; 1567c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1568b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int start = getChipStart(chip); 1569b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira int end = getChipEnd(chip); 1570b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira getSpannable().removeSpan(chip); 1571b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Editable editable = getText(); 1572b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira CharSequence chipText = createChip(entry, false); 1573b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (start == -1 || end == -1) { 1574b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira Log.e(TAG, "The chip to replace does not exist but should."); 1575b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira editable.insert(0, chipText); 1576b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1577b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // There may be a space to replace with this chip's new associated 1578b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // space. Check for it. 1579b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira int toReplace = end; 1580b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira while (toReplace >= 0 && toReplace < editable.length() 1581b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira && editable.charAt(toReplace) == ' ') { 1582b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira toReplace++; 1583b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira } 1584b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira editable.replace(start, toReplace, chipText); 1585c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1586b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira setCursorVisible(true); 1587b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (wasSelected) { 1588b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1589c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1590b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1591c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1592b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira /** 1593b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Handle click events for a chip. When a selected chip receives a click 1594b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * event, see if that event was in the delete icon. If so, delete it. 1595b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira * Otherwise, unselect the chip. 1596b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira */ 1597b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira public void onClick(RecipientChip chip, int offset, float x, float y) { 1598b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (chip.isSelected()) { 1599b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira if (isInDelete(chip, offset, x, y)) { 1600b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira removeChip(chip); 1601b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } else { 1602b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira clearSelectedChip(); 1603c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1604c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira } 1605b3f8574ad18184f5a9392fc7773863f59f9f5192Mindy Pereira } 1606c7ea4602c84a8fed20488337b8d1a00d8a205f6aMindy Pereira 1607368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira private boolean chipsPending() { 1608368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0); 1609368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira } 1610368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira 1611311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira @Override 1612311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira public void removeTextChangedListener(TextWatcher watcher) { 1613311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira mTextWatcher = null; 1614311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira super.removeTextChangedListener(watcher); 1615311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira } 1616311576adc68a068ab583d794e064329b0f71b8d1Mindy Pereira 1617e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira private class RecipientTextWatcher implements TextWatcher { 1618e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1619e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void afterTextChanged(Editable s) { 16201e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // If the text has been set to null or empty, make sure we remove 16211e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // all the spans we applied. 16221e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (TextUtils.isEmpty(s)) { 16231e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira // Remove all the chips spans. 16241e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira Spannable spannable = getSpannable(); 16251e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip[] chips = spannable.getSpans(0, getText().length(), 16261e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira RecipientChip.class); 16271e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira for (RecipientChip chip : chips) { 16281e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(chip); 16291e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 16301e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira if (mMoreChip != null) { 16311e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira spannable.removeSpan(mMoreChip); 16321e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 16331e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira return; 16341e70f1a4c71895213a1ea65a1e68ab9cc0b12ad2Mindy Pereira } 16351174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Get whether there are any recipients pending addition to the 16361174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // view. If there are, don't do anything in the text watcher. 1637368f569ffda5dc5fb85ee1eebc023759a46e31c8Mindy Pereira if (chipsPending()) { 1638e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira return; 1639e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1640e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mSelectedChip != null) { 1641e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setCursorVisible(true); 1642e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira setSelection(getText().length()); 1643e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira clearSelectedChip(); 1644e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1645e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int length = s.length(); 1646e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Make sure there is content there to parse and that it is 1647054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira // not just the commit character. 1648e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (length > 1) { 1649054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira char last; 165076ebe80e9fc58b31452d1a0724dd88d420a5b580Mindy Pereira int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1; 1651054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira int len = length() - 1; 1652054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira if (end != len) { 1653054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(end); 1654054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } else { 1655054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira last = s.charAt(len); 1656054b3caa7d695ff5bd6cebfb79067e731f334934Mindy Pereira } 1657e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) { 1658e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1659e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } else if (last == COMMIT_CHAR_SPACE) { 1660e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // Check if this is a valid email address. If it is, 1661e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira // commit it. 1662e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String text = getText().toString(); 1663e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd()); 1664e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text, 1665e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira tokenStart)); 1666e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira if (mValidator != null && mValidator.isValid(sub)) { 1667e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira commitByCharacter(); 1668e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1669e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1670e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1671e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1672e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1673e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1674e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void onTextChanged(CharSequence s, int start, int before, int count) { 16751174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira // Do nothing. 1676e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1677e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira 1678e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira @Override 1679e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira public void beforeTextChanged(CharSequence s, int start, int count, int after) { 1680e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 1681e0bf819fff34b7678dd336e4fd010f04bb343eeaMindy Pereira } 168277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 168377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> { 168477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private RecipientChip createFreeChip(RecipientEntry entry) { 168577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String displayText = entry.getDestination(); 16866f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira if (displayText.indexOf(",") == -1) { 16876f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira displayText = (String) mTokenizer.terminateToken(displayText); 16886f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 168977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira try { 169077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return constructChipSpan(entry, -1, false); 169177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } catch (NullPointerException e) { 169277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Log.e(TAG, e.getMessage(), e); 169377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 169477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 169577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 169677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 169777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 169877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Void... params) { 169977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mIndividualReplacements != null) { 170077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mIndividualReplacements.cancel(true); 170177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 170277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 170377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 170477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 170577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>(); 17066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip[] existingChips = getSortedRecipients(); 170777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < existingChips.length; i++) { 170877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.add(existingChips[i]); 170977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 171077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (mRemovedSpans != null) { 171177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.addAll(mRemovedSpans); 171277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 171377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 171477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 171577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira addresses[i] = originalRecipients.get(i).getEntry().getDestination(); 171677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 171777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 171877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 171977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>(); 172077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 172177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira RecipientEntry entry = null; 17221174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 172377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 172477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 17251174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry() 17261174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .getDestination()))); 172777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 172877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 172977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(createFreeChip(entry)); 173077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } else { 173177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replacements.add(temp); 173277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 173377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 173477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (replacements != null && replacements.size() > 0) { 173577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 173677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 173777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 173877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira SpannableStringBuilder text = new SpannableStringBuilder(getText() 173977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .toString()); 174077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable oldText = getText(); 174177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int start, end; 174277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira int i = 0; 174377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (RecipientChip chip : originalRecipients) { 174477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira start = oldText.getSpanStart(chip); 174577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (start != -1) { 1746b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira end = oldText.getSpanEnd(chip); 1747b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira text.removeSpan(chip); 1748b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira // Leave a spot for the space! 17496f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira RecipientChip replacement = replacements.get(i); 17506f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira text.setSpan(replacement, start, end, 1751b1dd0ac0f11ff3d6352f67311f14aa95185d224bMindy Pereira Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17526f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira replacement.setOriginalText(text.toString().substring(start, end)); 175377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 175477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira i++; 175577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 175677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira Editable editable = getText(); 175777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.clear(); 175877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira editable.insert(0, text); 175977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira originalRecipients.clear(); 176077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 176277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 176477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 176677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira 176777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> { 176877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @SuppressWarnings("unchecked") 176977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 177077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira protected Void doInBackground(Object... params) { 177177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // For each chip in the list, look up the matching contact. 177277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // If there is a match, replace that chip with the matching 177377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // chip. 177477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira final ArrayList<RecipientChip> originalRecipients = 177577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira (ArrayList<RecipientChip>) params[0]; 177677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira String[] addresses = new String[originalRecipients.size()]; 177777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (int i = 0; i < originalRecipients.size(); i++) { 177877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira addresses[i] = originalRecipients.get(i).getEntry().getDestination(); 177977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 178077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter 178177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira .getMatchingRecipients(getContext(), addresses); 178277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira for (final RecipientChip temp : originalRecipients) { 17831174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId()) 178477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira && getSpannable().getSpanStart(temp) != -1) { 178577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira // Replace this. 17861174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira final RecipientEntry entry = createValidatedEntry(entries 17871174eca98d1dbd263a9e72c5a9e35a1039aae979Mindy Pereira .get(tokenizeAddress(temp.getEntry().getDestination()))); 178877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira if (entry != null) { 178977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira mHandler.post(new Runnable() { 179077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira @Override 179177056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira public void run() { 179277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira replaceChip(temp, entry); 179377056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 179477056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira }); 179577056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 179677056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 179777056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 179877056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira return null; 179977056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 180077056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira } 1801b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 18026f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 18036f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira /** 18046f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * MoreImageSpan is a simple class created for tracking the existence of a 18056f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira * more chip across activity restarts/ 18066f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira */ 18076f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira private class MoreImageSpan extends ImageSpan { 18086f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira public MoreImageSpan(Drawable b) { 18096f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira super(b); 18106f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 18116f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira } 18126f9f2858818228eaa1f2a2c562f4d2da6a4216b3Mindy Pereira 1813b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1814b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onDown(MotionEvent e) { 1815b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1816b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1817b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1818b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1819b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 1820b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1821b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1822b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1823b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1824b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1825b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onLongPress(MotionEvent event) { 1826b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (mSelectedChip != null) { 1827b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return; 1828b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1829b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float x = event.getX(); 1830b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira float y = event.getY(); 1831b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira int offset = putOffsetInRange(getOffsetForPosition(x, y)); 1832b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira RecipientChip currentChip = findChip(offset); 1833b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira if (currentChip != null) { 1834b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy the selected chip email address. 1835b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira showCopyDialog(currentChip.getEntry().getDestination()); 1836b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1837b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1838b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1839b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira private void showCopyDialog(final String address) { 1840b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = address; 1841b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog = new Dialog(getContext()); 1842b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setTitle(address); 1843b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setContentView(mCopyViewRes); 1844b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCancelable(true); 1845b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setCanceledOnTouchOutside(true); 1846b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.findViewById(android.R.id.button1).setOnClickListener(this); 1847b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.setOnDismissListener(this); 1848b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.show(); 1849b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1850b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1851b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1852b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 1853b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1854b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1855b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1856b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1857b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1858b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onShowPress(MotionEvent e) { 1859b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1860b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1861b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1862b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1863b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public boolean onSingleTapUp(MotionEvent e) { 1864b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Do nothing. 1865b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira return false; 1866b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1867b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1868b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1869b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onDismiss(DialogInterface dialog) { 1870b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyAddress = null; 1871b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog = null; 1872b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 1873b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira 1874b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira @Override 1875b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira public void onClick(View v) { 1876b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira // Copy this to the clipboard. 1877b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService( 1878b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira Context.CLIPBOARD_SERVICE); 1879b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress)); 1880b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira mCopyDialog.dismiss(); 1881b8208f24b2768acf369ad58309031feac87ce79cMindy Pereira } 188277056d7532cd26e869964a52456ef18c96f6cbd7Mindy Pereira} 1883